File new-session-manager-1.6.0+git.20220415.0f6719c.obscpio of Package new-session-manager

07070100000000000081A40000000000000000000000016258A65C00000073000000000000000000000000000000000000003A00000000new-session-manager-1.6.0+git.20220415.0f6719c/.gitignore*~
*.bak
*.swp
*.[ao]
TAGS
.nfs*
build
builddir
subprojects
# Ignore (compiled) python files,
*__pycache__/*
*.pyc
07070100000001000081A40000000000000000000000016258A65C000001F1000000000000000000000000000000000000003700000000new-session-manager-1.6.0+git.20220415.0f6719c/AUTHORS# A complete list of authors. For individual contributions please read CHANGELOG

Liles, Jonathan Moore / original-male  (original author)
    for authors before the fork in May 2020 please see https://github.com/original-male/non

Coelho, Filipe  / falktx (new-session-manager fork)
Hilbricht, Nils  (new-session-manager fork)

# Individual Contributions in alphabetical surname order

Meyer, Hermann / brummer
Picot, Mathieu  / houston
Berkelder, Rik
Runge, David  / dvzrv
 / TheGreatWhiteShark
07070100000002000081A40000000000000000000000016258A65C00001771000000000000000000000000000000000000003900000000new-session-manager-1.6.0+git.20220415.0f6719c/CHANGELOG#Changelog
Format: Double ## for a version number followed by a space, ISO-Date, Semantic Versioning:
## YYYY-MM-DD major.minor.patch
Two empty lines before the next entry.
External contributors notice at the end of the line: (LastName, FirstName / nick)


## 2022-04-15 1.6.0
nsmd:
 Now follows the XDG Base Directory Specifications.
 Default session directory moved from ~/NSM Sessions/ to $XDG_DATA_HOME/nsm/ (see issue #gh-15)
 The old path ~/NSM Sessions/ is still supported and has priority, for now. This may be switched off in the future.

 Lockfiles fixed (see issue #gh-31)
 Lockfiles are now in $XDG_RUNTIME_DIR/nsm/
 Lockfiles now each contain the session path, the osc NSM_URL and the nsmd PID
 One daemon file for each currently running nsmd is created in $XDG_RUNTIME_DIR/nsm/d/ containing the osc url. This enables discovery of running daemons.

 New section in the API documentation for the above.
 Handle write-protected session files and related errors on save. They will not crash the daemon anymore.
 Fixes and guards against trying to load non-existing sessions and creating new sessions under existing names
 Handle various crashes-on-exit and replace them with controlled exits.

Jackpatch Version 1.0.0 (previously 0.2.0):
  Jackpatch will finally not "forget" connections anymore. See #gh-74
  Reduce verbosity level of log ouput.
  Document 'hidden' standalone (no NSM) command line mode in --help
  Handle SIGNALs even when in standalone mode
  Add a jackpatch desktop file with X-NSM-Capable=true and X-NSM-Exec=jackpatch and NoDisplay=true

NSM-Proxy:
    Add a nsm-proxy desktop file with X-NSM-Capable=true and X-NSM-Exec=nsm-proxy and NoDisplay=true


## 2022-01-15 1.5.3
Add [jackpatch] to terminal log output of jackpatch.
Remove hardcoded ANSI colors from terminal log output


## 2021-07-15 1.5.2
pynsm2 library: Fixed a rare crash, where the hostname must be case sensitive but Pythons urlparse forced lower-case.


## 2021-03-19 1.5.1
Web-URLs changed to https://new-session-manager.jackaudio.org and https://github.com/jackaudio/new-session-manager
No codechanges.


## 2021-01-15 1.5.0
WARNING: Next scheduled release (2021-04-15) will switch the default session root
to $XDG_DATA_HOME ( default on most distributions: ~/.local/share/nsm/ )
With the next release prepare to do one of the following:
*  Move old sessions to the new root directory (preferred)
*  Symlink "~/NSM Sessions" to the new root directory
*  use the nsmd --session-root commandline argument.

nsmd:
 Fix session discovery to not report nested sessions anymore. Also more robust file system error handling.
 Command line option --quiet: Suppress messages except warnings and errors
 Protect against orphaned clients or daemons when the server, or even a GUI, crashes.
 Replace cowboy-slang in info-level OSC with descriptive, technical messages.

Legacy-GUI:
 Fix manpage description and usage with the correct executable name
 Fix resizing to very small and back. ( / TheGreatWhiteShark )

NSM-Proxy:
  Multiple layout and style fixes. Better texts for beginners.

API:
 NSM_API_VERSION_PATCH from 0 to 1 (1.1.0 -> 1.1.1)
 Please see API document chapter "Changes in API Version 1.1.1"

Extras:
 This repository now contains extras (libraries, programs, documentation etc.)
 Extras are technically not connected to the main programs of this repository.
 There is no dependency to any "extra" nor any license implications.
 Please read extras/README.md.

 nsm.h was moved to extras/nsm.h
 "extras/pynsm" is now a part of NEW-SM. It was a standalone git repo until now.


## 2020-07-15 1.4.0
Add documentation and manpages.

Legacy-GUI:
 Overhaul look and feel.
 Rewrite labels and buttons with unambiguous descriptions.
 Protect text-input dialog windows from empty strings, like "Add New Client" or "New Session"
 Scale icons, support more icon formats.
 Show all icons and buttons when attaching to a running nsmd session
 Various small fixes.
 Always show correct session name, no matter how the session was loaded or how the GUI was started

nsmd:
 NSM_API_VERSION_MINOR from 0 to 1 (1.0 -> 1.1)
 Repair nsmd to correctly send client data when running headless and a GUI announces later.
 ClientId generation now prevent collision with existing IDs.
 nsmd command line option --load-session to directly load one (Berkelder, Rik)
 Better detection of clients that failed to launch leads to faster session startup (by 5 seconds)
 Users get informed by client-label if an executable is not present on the system or permission denied
 Fixed reply for listing sessions from a plain "Done." to proper reply path with empty string as terminal symbol  "/reply", "/nsm/server/list", ""
 Fix operation reply to last treated client instead to reply to sender (Picot, Mathieu  / houston)
 /nsm/gui/session/name send consistent session name/relative-path pair to the annouced GUI, no matter how the session was loaded.

nsm.h
 :optional-gui: support to nsm.h, for other applications to include and use. (Meyer, Hermann / brummer)


## 2020-06-20 1.3.2
Rename new-session-manager executable to nsm-legacy-gui to prevent future confusion.


## 2020-06-20 1.3.1
Add header copyright even to unchanged files to adhere to stricter packaging requirements.
Meson can now switch off individual executables and will stop/error when trying to build without dependencies.


## 2020-06-17 1.3.0
Rebranding to "new session manager"
Upstream GUI tools "non-session-manager" and "nsm-proxy" converted to standard FLTK instead of a custom toolkit
Changed build system to meson
License upgraded to GPLv3
Simplified file structure
Fix compiler warnings.


## 2020-04-19 1.2.1
Current state of upstream Non Session Manager v1.2 including unreleased /nsm/gui/session/root


## 2017-07-08 1.2.0
Last release of Non-Session-Manager.
Commit 1904aba516341287ac297cefbbcd185f643e5538


## 2012-03-03 1.1.0
Initial release of Non-Session-Manager.
https://non.tuxfamily.org/wiki/2012-03-03%20Release%20Announcement%20v1.1.0
07070100000003000081A40000000000000000000000016258A65C0000894D000000000000000000000000000000000000003700000000new-session-manager-1.6.0+git.20220415.0f6719c/COPYING                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.  We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors.  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights.  Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received.  You must make sure that they, too, receive
or can get the source code.  And you must show them these terms so they
know their rights.

  Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

  For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

  Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so.  This is fundamentally incompatible with the aim of
protecting users' freedom to change the software.  The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable.  Therefore, we
have designed this version of the GPL to prohibit the practice for those
products.  If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

  Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary.  To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Use with the GNU Affero General Public License.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU 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
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    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 <https://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

    <program>  Copyright (C) <year>  <name of author>
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.

  The GNU General Public License does not permit incorporating your program
into proprietary programs.  If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.  But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
07070100000004000081A40000000000000000000000016258A65C000016AF000000000000000000000000000000000000003900000000new-session-manager-1.6.0+git.20220415.0f6719c/README.md# New Session Manager

## Introduction

New Session Manager (NSM) is a tool to assist music production by grouping standalone programs into
sessions. Your workflow becomes easy to manage, robust and fast by leveraging the full potential of
cooperative applications.

NSM continues to be free in every sense of the word: free of cost, free to share and use, free of
spyware or ads, free-and-open-source.

You can create a session, or project, add programs to it and then use commands to save, start/stop,
hide/show all programs at once, or individually. At a later date you can then re-open the session
and continue where you left off.

All files belonging to the session will be saved in the same directory.

If you are a user (and not a programmer or packager) everything you need is to install NSM
through your distributions package manager and, highly recommended, Agordejo as a GUI (see below).

To learn NSM you don't need to know the background information from our documentation, which
is aimed at developers that want to implement NSM support in their programs. Learn the GUI,
not the server and protocol.


## Bullet Points
* Drop-In replacement for the non-session-manager daemon nsmd and tools (e.g. jackpatch)
* Simple and hassle-free build system to make packaging easy
* Possibility to react to sensible bug fixes that would not have been integrated original nsmd
* Stay upwards and downwards compatible with original nsmd
* Conservative and hesitant in regards to new features and behaviour-changes, but possible in principle
* Keep the session-manager separate from the other NON* tools Mixer, Sequencer and Timeline.
* Protect nsmd from vanishing from the internet one day.
* The goal is to become the de-facto standard music session manager for Linux distributions

## User Interface
It is highly recommended to use Agordejo ( https://www.laborejo.org/agordejo/ ) as graphical
user interface. In fact, if you install Agordejo in your distribution it will install NSM as
dependency and you don't need to do anything yourself with this software package.

This repository also contains the legacy FLTK interface simply called `nsm-legacy-gui`,
symlinked to `non-session-manager` for backwards compatibility. (e.g. autostart scripts etc.)

## Supported Clients

While NSM can start and stop any program it only becomes convenient if clients specifically
implement support. This enables saving and hiding the GUI, amongst other features.
Documentation and tutorials for software-developers will be added at a later date.

## Documentation and Manual

Our documentation contains the API specification for the NSM protocol, which is the central document
if you want to add NSM support to your own application.

You can find html documentation installed to your systems SHARE dir or docs/out/index.html in this
repository.
There is also an online version https://jackaudio.github.io/new-session-manager/

We also provide a set of manpages for each executable (see Build).


## Fork and License
This is a fork of non-session-manager, by Jonathan Moore Liles <male@tuxfamily.net> http://non.tuxfamily.org/
which was released under the GNU GENERAL PUBLIC LICENSE  Version 2, June 1991.

All files, except nsm.h kept in this fork were GPL "version 2 of the License, or (at your
option) any later version."

`nsm.h` is licensed under the ISC License.

New-Session-Manager changed the license to GNU GENERAL PUBLIC LICENSE, Version 3, 29 June 2007.
See file COPYING

Documentation in docs/ is licensed Creative Commons CC-By-Sa.
It consist of mostly generated files and snippet files which do not have a license header for
technical reasons.
All original documentation source files are CC-By-Sa Version v4.0 (see file docs/src/LICENSE),
the source file docs/src/api/index.adoc is a derived work from NON-Session-Managers API file which
is licensed CC-By-Sa v2.5. Therefore our derived API document is also CC-By-Sa v2.5
(see files docs/src/api/readme.txt and docs/src/api/LICENSE)


## Build
The build system is meson.

This is a software package that will compile and install multiple executables:
* `nsmd`, the daemon or server itself. It is mandatory.
  * It has no GUI.
  * Dependency is `liblo`, the OSC library.
* `jackpatch`, NSM client to save and remember JACK connections.
  * It has no GUI.
  * Dependencies are `JACK Audio Connection Kit` and `liblo`, the OSC library.
  * Can be deactivated (see below) `-Djackpatch=false`
* `nsm-legacy-gui`, Legacy GUI for the user
  * Formerly known as "non-session-manager"
  * Dependencies are `FLTK`>=v1.3.0 and `liblo`, the OSC library.
  * Can be deactivated (see below) `-Dlegacy-gui=false`
* `nsm-proxy`, NSM GUI Client to run any program without direct NSM support
  * Dependencies are `FLTK`>=v1.3.0, `fluid` (FLTK Editor/compiler, maybe in the same package as FLTK, maybe not) and `liblo`, the OSC library.
  * Can be deactivated (see below) `-Dnsm-proxy=false`


```
meson build --prefix=/usr
#or disable individual build targets:
#meson build --prefix=/usr -Dlegacy-gui=false -Dnsm-proxy=false -Djackpatch=false
cd build && ninja
sudo ninja install
```

Optionally you can skip `sudo ninja install` and run all executables from the build-dir.
In this case you need to add the build-dir to your PATH environment variable so that the tools
can find each other.

## Names of Executable Files and Symlinks

Some distributions (and possibly local laws) prevent a forked software project from creating
executable files under the same name, if the name itself is an original work subject to copyright,
which it arguably is for the "NON-"-suite. Therefore New Session Manager renamed
`non-session-manager` to `nsm-legacy-gui`. Installing will also create a symlink to
`non-session-manager` for backwards compatibility. (e.g. autostart scripts etc.).
07070100000005000041ED0000000000000000000000046258A65C00000000000000000000000000000000000000000000003400000000new-session-manager-1.6.0+git.20220415.0f6719c/docs07070100000006000081A40000000000000000000000016258A65C00000021000000000000000000000000000000000000003A00000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/CNAMEnew-session-manager.jackaudio.org07070100000007000041ED0000000000000000000000026258A65C00000000000000000000000000000000000000000000003800000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/api07070100000008000081A40000000000000000000000016258A65C000169BF000000000000000000000000000000000000004300000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/api/index.html<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 2.0.17">
<meta name="author" content="Jonathan Moore Liles, Nils Hilbricht">
<title>New Session Manager - API</title>
<style>
/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
/* Uncomment the following line when using as a custom stylesheet */
/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
html{font-family:sans-serif;-webkit-text-size-adjust:100%}
a{background:none}
a:focus{outline:thin dotted}
a:active,a:hover{outline:0}
h1{font-size:2em;margin:.67em 0}
b,strong{font-weight:bold}
abbr{font-size:.9em}
abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
dfn{font-style:italic}
hr{height:0}
mark{background:#ff0;color:#000}
code,kbd,pre,samp{font-family:monospace;font-size:1em}
pre{white-space:pre-wrap}
q{quotes:"\201C" "\201D" "\2018" "\2019"}
small{font-size:80%}
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sup{top:-.5em}
sub{bottom:-.25em}
img{border:0}
svg:not(:root){overflow:hidden}
figure{margin:0}
audio,video{display:inline-block}
audio:not([controls]){display:none;height:0}
fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
legend{border:0;padding:0}
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
button,input{line-height:normal}
button,select{text-transform:none}
button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
button[disabled],html input[disabled]{cursor:default}
input[type=checkbox],input[type=radio]{padding:0}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
textarea{overflow:auto;vertical-align:top}
table{border-collapse:collapse;border-spacing:0}
*,::before,::after{box-sizing:border-box}
html,body{font-size:100%}
body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
a:hover{cursor:pointer}
img,object,embed{max-width:100%;height:auto}
object,embed{height:100%}
img{-ms-interpolation-mode:bicubic}
.left{float:left!important}
.right{float:right!important}
.text-left{text-align:left!important}
.text-right{text-align:right!important}
.text-center{text-align:center!important}
.text-justify{text-align:justify!important}
.hide{display:none}
img,object,svg{display:inline-block;vertical-align:middle}
textarea{height:auto;min-height:50px}
select{width:100%}
.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
a{color:#2156a5;text-decoration:underline;line-height:inherit}
a:hover,a:focus{color:#1d4b8f}
a img{border:0}
p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
p aside{font-size:.875em;line-height:1.35;font-style:italic}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
h1{font-size:2.125em}
h2{font-size:1.6875em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
h4,h5{font-size:1.125em}
h6{font-size:1em}
hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
em,i{font-style:italic;line-height:inherit}
strong,b{font-weight:bold;line-height:inherit}
small{font-size:60%;line-height:inherit}
code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
ul,ol{margin-left:1.5em}
ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}
ul.square{list-style-type:square}
ul.circle{list-style-type:circle}
ul.disc{list-style-type:disc}
ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
dl dt{margin-bottom:.3125em;font-weight:bold}
dl dd{margin-bottom:1.25em}
blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
h1{font-size:2.75em}
h2{font-size:2.3125em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
h4{font-size:1.4375em}}
table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
table thead,table tfoot{background:#f7f8f7}
table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
table tr.even,table tr.alt{background:#f8f8f7}
table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
.center{margin-left:auto;margin-right:auto}
.stretch{width:100%}
.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
.clearfix::after,.float-group::after{clear:both}
:not(pre).nobreak{word-wrap:normal}
:not(pre).nowrap{white-space:nowrap}
:not(pre).pre-wrap{white-space:pre-wrap}
:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
pre>code{display:block}
pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
em em{font-style:normal}
strong strong{font-weight:400}
.keyseq{color:rgba(51,51,51,.8)}
kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
.keyseq kbd:first-child{margin-left:0}
.keyseq kbd:last-child{margin-right:0}
.menuseq,.menuref{color:#000}
.menuseq b:not(.caret),.menuref{font-weight:inherit}
.menuseq{word-spacing:-.02em}
.menuseq b.caret{font-size:1.25em;line-height:.8}
.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
b.button::before{content:"[";padding:0 3px 0 2px}
b.button::after{content:"]";padding:0 2px 0 3px}
p a>code:hover{color:rgba(0,0,0,.9)}
#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
#content{margin-top:1.25em}
#content::before{content:none}
#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
#header .details span:first-child{margin-left:-.125em}
#header .details span.email a{color:rgba(0,0,0,.85)}
#header .details br{display:none}
#header .details br+span::before{content:"\00a0\2013\00a0"}
#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
#header .details br+span#revremark::before{content:"\00a0|\00a0"}
#header #revnumber{text-transform:capitalize}
#header #revnumber::after{content:"\00a0"}
#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
#toc>ul{margin-left:.125em}
#toc ul.sectlevel0>li>a{font-style:italic}
#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
#toc li{line-height:1.3334;margin-top:.3334em}
#toc a{text-decoration:none}
#toc a:active{text-decoration:underline}
#toctitle{color:#7a2518;font-size:1.2em}
@media screen and (min-width:768px){#toctitle{font-size:1.375em}
body.toc2{padding-left:15em;padding-right:0}
#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
#toc.toc2>ul{font-size:.9em;margin-bottom:0}
#toc.toc2 ul ul{margin-left:0;padding-left:1em}
#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
body.toc2.toc-right{padding-left:0;padding-right:15em}
body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
#toc.toc2{width:20em}
#toc.toc2 #toctitle{font-size:1.375em}
#toc.toc2>ul{font-size:.95em}
#toc.toc2 ul ul{padding-left:1.25em}
body.toc2.toc-right{padding-left:0;padding-right:20em}}
#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
#content #toc>:first-child{margin-top:0}
#content #toc>:last-child{margin-bottom:0}
#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
#content{margin-bottom:.625em}
.sect1{padding-bottom:.625em}
@media screen and (min-width:768px){#content{margin-bottom:1.25em}
.sect1{padding-bottom:1.25em}}
.sect1:last-child{padding-bottom:0}
.sect1+.sect1{border-top:1px solid #e7e7e9}
#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
details{margin-left:1.25rem}
details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
details>summary::-webkit-details-marker{display:none}
details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
.admonitionblock>table td.icon{text-align:center;width:80px}
.admonitionblock>table td.icon img{max-width:none}
.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
.exampleblock>.content>:first-child{margin-top:0}
.exampleblock>.content>:last-child{margin-bottom:0}
.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
.sidebarblock>:first-child{margin-top:0}
.sidebarblock>:last-child{margin-bottom:0}
.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
.listingblock>.content{position:relative}
.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
.listingblock:hover code[data-lang]::before{display:block}
.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
.listingblock pre.highlightjs{padding:0}
.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
.listingblock pre.prettyprint{border-width:0}
.prettyprint{background:#f7f7f8}
pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
pre.prettyprint li code[data-lang]::before{opacity:1}
pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
table.linenotable td.code{padding-left:.75em}
table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
pre.pygments span.linenos{display:inline-block;margin-right:.75em}
.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
.quoteblock blockquote{margin:0;padding:0;border:0}
.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
.verseblock{margin:0 1em 1.25em}
.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
.verseblock pre strong{font-weight:400}
.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
.quoteblock .attribution br,.verseblock .attribution br{display:none}
.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
.quoteblock.abstract{margin:0 1em 1.25em;display:block}
.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
p.tableblock:last-child{margin-bottom:0}
td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
td.tableblock>.content>:last-child{margin-bottom:-1.25em}
table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
table.grid-all>*>tr>*{border-width:1px}
table.grid-cols>*>tr>*{border-width:0 1px}
table.grid-rows>*>tr>*{border-width:1px 0}
table.frame-all{border-width:1px}
table.frame-ends{border-width:1px 0}
table.frame-sides{border-width:0 1px}
table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
th.halign-left,td.halign-left{text-align:left}
th.halign-right,td.halign-right{text-align:right}
th.halign-center,td.halign-center{text-align:center}
th.valign-top,td.valign-top{vertical-align:top}
th.valign-bottom,td.valign-bottom{vertical-align:bottom}
th.valign-middle,td.valign-middle{vertical-align:middle}
table thead th,table tfoot th{font-weight:bold}
tbody tr th{background:#f7f8f7}
tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
p.tableblock>code:only-child{background:none;padding:0}
p.tableblock{font-size:1em}
ol{margin-left:1.75em}
ul li ol{margin-left:1.5em}
dl dd{margin-left:1.125em}
dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
ul.unstyled,ol.unstyled{margin-left:0}
li>p:empty:only-child::before{content:"";display:inline-block}
ul.checklist>li>p:first-child{margin-left:-1em}
ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
ul.inline>li{margin-left:1.25em}
.unstyled dl dt{font-weight:400;font-style:normal}
ol.arabic{list-style-type:decimal}
ol.decimal{list-style-type:decimal-leading-zero}
ol.loweralpha{list-style-type:lower-alpha}
ol.upperalpha{list-style-type:upper-alpha}
ol.lowerroman{list-style-type:lower-roman}
ol.upperroman{list-style-type:upper-roman}
ol.lowergreek{list-style-type:lower-greek}
.hdlist>table,.colist>table{border:0;background:none}
.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
td.hdlist1{font-weight:bold;padding-bottom:1.25em}
td.hdlist2{word-wrap:anywhere}
.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
.colist td:not([class]):first-child img{max-width:none}
.colist td:not([class]):last-child{padding:.25em 0}
.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
.imageblock.left{margin:.25em .625em 1.25em 0}
.imageblock.right{margin:.25em 0 1.25em .625em}
.imageblock>.title{margin-bottom:0}
.imageblock.thumb,.imageblock.th{border-width:6px}
.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
.image.left{margin-right:.625em}
.image.right{margin-left:.625em}
a.image{text-decoration:none;display:inline-block}
a.image object{pointer-events:none}
sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
sup.footnote a,sup.footnoteref a{text-decoration:none}
sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
#footnotes .footnote:last-of-type{margin-bottom:0}
#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
div.unbreakable{page-break-inside:avoid}
.big{font-size:larger}
.small{font-size:smaller}
.underline{text-decoration:underline}
.overline{text-decoration:overline}
.line-through{text-decoration:line-through}
.aqua{color:#00bfbf}
.aqua-background{background:#00fafa}
.black{color:#000}
.black-background{background:#000}
.blue{color:#0000bf}
.blue-background{background:#0000fa}
.fuchsia{color:#bf00bf}
.fuchsia-background{background:#fa00fa}
.gray{color:#606060}
.gray-background{background:#7d7d7d}
.green{color:#006000}
.green-background{background:#007d00}
.lime{color:#00bf00}
.lime-background{background:#00fa00}
.maroon{color:#600000}
.maroon-background{background:#7d0000}
.navy{color:#000060}
.navy-background{background:#00007d}
.olive{color:#606000}
.olive-background{background:#7d7d00}
.purple{color:#600060}
.purple-background{background:#7d007d}
.red{color:#bf0000}
.red-background{background:#fa0000}
.silver{color:#909090}
.silver-background{background:#bcbcbc}
.teal{color:#006060}
.teal-background{background:#007d7d}
.white{color:#bfbfbf}
.white-background{background:#fafafa}
.yellow{color:#bfbf00}
.yellow-background{background:#fafa00}
span.icon>.fa{cursor:default}
a span.icon>.fa{cursor:inherit}
.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
.conum[data-value] *{color:#fff!important}
.conum[data-value]+b{display:none}
.conum[data-value]::after{content:attr(data-value)}
pre .conum[data-value]{position:relative;top:-.125em}
b.conum *{color:inherit!important}
.conum:not([data-value]):empty{display:none}
dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
p,blockquote,dt,td.content,span.alt,summary{font-size:1.0625rem}
p{margin-bottom:1.25rem}
.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
.print-only{display:none!important}
@page{margin:1.25cm .75cm}
@media print{*{box-shadow:none!important;text-shadow:none!important}
html{font-size:80%}
a{color:inherit!important;text-decoration:underline!important}
a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
abbr[title]{border-bottom:1px dotted}
abbr[title]::after{content:" (" attr(title) ")"}
pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
thead{display:table-header-group}
svg{max-width:100%}
p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
#header,#content,#footnotes,#footer{max-width:none}
#toc,.sidebarblock,.exampleblock>.content{background:none!important}
#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
body.book #header{text-align:center}
body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
body.book #header .details{border:0!important;display:block;padding:0!important}
body.book #header .details span:first-child{margin-left:0!important}
body.book #header .details br{display:block}
body.book #header .details br+span::before{content:none!important}
body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
.listingblock code[data-lang]::before{display:block}
#footer{padding:0 .9375em}
.hide-on-print{display:none!important}
.print-only{display:block!important}
.hide-for-print{display:none!important}
.show-for-print{display:inherit!important}}
@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
.sect1{padding:0!important}
.sect1+.sect1{border:0}
#footer{background:none}
#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
</style>
</head>
<body class="article">
<div id="header">
<h1>New Session Manager - API</h1>
<div class="details">
<span id="author" class="author">Jonathan Moore Liles, Nils Hilbricht</span><br>
<span id="revnumber">version API 1.1.2</span>
<br><span id="revremark">License CC-By-SA v2.5</span>
</div>
<div id="toc" class="toc">
<div id="toctitle">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#_client_behavior_under_session_management">1. Client Behavior Under Session Management</a>
<ul class="sectlevel2">
<li><a href="#_file_menu">1.1. File Menu</a>
<ul class="sectlevel3">
<li><a href="#_new">1.1.1. New</a></li>
<li><a href="#_open">1.1.2. Open</a></li>
<li><a href="#_save">1.1.3. Save</a></li>
<li><a href="#_save_as">1.1.4. Save As</a></li>
<li><a href="#_close_as_distinguished_from_quit_or_exit">1.1.5. Close (as distinguished from Quit or Exit)</a></li>
<li><a href="#_quit_or_exit">1.1.6. Quit or Exit</a></li>
</ul>
</li>
<li><a href="#_data_storage">1.2. Data Storage</a>
<ul class="sectlevel3">
<li><a href="#_internal_files">1.2.1. Internal Files</a></li>
<li><a href="#_external_files">1.2.2. External Files</a></li>
<li><a href="#_session_root_and_session_directories">1.2.3. Session Root and Session Directories</a>
<ul class="sectlevel4">
<li><a href="#_subdirectories_hierarchical_structure">1.2.3.1. Subdirectories / Hierarchical Structure</a></li>
<li><a href="#_write_protection_for_session_templates">1.2.3.2. Write-Protection for Session Templates</a></li>
<li><a href="#_lockfiles">1.2.3.3. Lockfiles</a></li>
<li><a href="#_daemon_discovery">1.2.3.4. Daemon Discovery</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><a href="#_nsm_osc_protocol">2. NSM OSC Protocol</a>
<ul class="sectlevel2">
<li><a href="#_establishing_a_connection">2.1. Establishing a Connection</a>
<ul class="sectlevel3">
<li><a href="#_announce">2.1.1. Announce</a></li>
<li><a href="#_response">2.1.2. Response</a></li>
</ul>
</li>
<li><a href="#_server_to_client_control_messages">2.2. Server to Client Control Messages</a>
<ul class="sectlevel3">
<li><a href="#_quit">2.2.1. Quit</a></li>
<li><a href="#server-to-client-control-messages-open">2.2.2. Open</a>
<ul class="sectlevel4">
<li><a href="#_response_2">2.2.2.1. Response</a></li>
</ul>
</li>
<li><a href="#_save_2">2.2.3. Save</a>
<ul class="sectlevel4">
<li><a href="#_response_3">2.2.3.1. Response</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#_server_to_client_informational_messages">2.3. Server to Client Informational Messages</a>
<ul class="sectlevel3">
<li><a href="#_session_is_loaded">2.3.1. Session is Loaded</a></li>
<li><a href="#_show_optional_gui">2.3.2. Show Optional Gui</a></li>
</ul>
</li>
<li><a href="#_client_to_server_informational_messages">2.4. Client to Server Informational Messages</a>
<ul class="sectlevel3">
<li><a href="#_optional_gui">2.4.1. Optional GUI</a></li>
<li><a href="#_progress">2.4.2. Progress</a></li>
<li><a href="#_dirtiness">2.4.3. Dirtiness</a></li>
<li><a href="#_status_messsages">2.4.4. Status Messsages</a></li>
</ul>
</li>
<li><a href="#_error_code_definitions">2.5. Error Code Definitions</a></li>
<li><a href="#_client_to_server_control">2.6. Client to Server Control</a></li>
<li><a href="#_server_control_api">2.7. Server Control API</a>
<ul class="sectlevel3">
<li><a href="#_client_to_client_communication">2.7.1. Client to Client Communication</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#_api_versions_and_behaviour_changes">3. API Versions and Behaviour Changes</a>
<ul class="sectlevel2">
<li><a href="#_guidelines">3.1. Guidelines</a></li>
<li><a href="#_changes_in_api_version_1_1_0">3.2. Changes in API Version 1.1.0</a></li>
<li><a href="#_changes_in_api_version_1_1_1">3.3. Changes in API Version 1.1.1</a></li>
<li><a href="#_changes_in_api_version_1_1_2">3.4. Changes in API Version 1.1.2</a></li>
</ul>
</li>
</ul>
</div>
</div>
<div id="content">
<div id="preamble">
<div class="sectionbody">
<div class="admonitionblock important">
<table>
<tr>
<td class="icon">
<div class="title">Important</div>
</td>
<td class="content">
"New Session Manager" is a community version of the
<a href="http://non.tuxfamily.org/nsm/API.html">"Non Session Manager" by Jonathan Moore Liles</a>, who also
wrote the majority of this API document, especially the API itself. <strong>The API is the same</strong>. Any
technical changes or differences in behaviour are described in <a href="#_api_versions_and_behaviour_changes">API Versions and Behaviour Changes</a>.
All other changes to this document can be reviewed by accessing the git log. This document is
licensed under CC-By-Sa v2.5. See <a href="https://github.com/jackaudio/new-session-manager/tree/master/docs/src/api">LICENSE</a>
</td>
</tr>
</table>
</div>
<div class="admonitionblock important">
<table>
<tr>
<td class="icon">
<div class="title">Important</div>
</td>
<td class="content">
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD
NOT", "RECOMMENDED",  "MAY", and "OPTIONAL" in this document are to be interpreted as
described in <a href="https://tools.ietf.org/html/rfc2119">RFC 2119</a>.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>The "New Session Manager"-API is used by many music and audio programs in Linux distributions
to allow any number of independent programs to be managed together as part of a logical session
(i.e. a song). Thus, operations such as loading and saving are synchronized.</p>
</div>
<div class="paragraph">
<p>The API comprises a simple Open Sound Control (OSC) based protocol, along with some behavioral
guidelines, which can easily be implemented by various applications.</p>
</div>
<div class="paragraph">
<p>This project contains a program called <code>nsmd</code> which is an implementation of the server side of
the NSM API. <code>nsmd</code> can be controlled by direct OSC messages, or (more commonly) a GUI:
Included in this package is the <code>nsm-legacy-gui</code>, which gets symlinked to "non-session-manager`.
Another GUI is "Agordejo". Other applications exist that (partially) support the NSM API and are able
to load clients, but they do not use the New-Session-Manager (or Non-Session-Manager) implementation
and are therefore out of scope for this document.</p>
</div>
<div class="paragraph">
<p>However, the same server-side API can also be implemented by other programs (such as Carla),
although consistency and robustness will likely suffer if non-NSM compliant clients are allowed to
participate in a session.</p>
</div>
<div class="paragraph">
<p>There is no direct dependency for client implementations, as long as they
can send and receive OSC.
Some clients use <code>liblo</code> (the OSC library), which becomes a dependency if you choose to implement
NSM-support with the provided header file <code>nsm.h</code> (<code>extras/nsm.h/nsm.h</code> in the git repository).
Some clients use the provided single-file python library <code>pynsm</code> (<code>extras/pynsm/nsmclient.py</code> in the git repository)
which has no dependencies outside the Python3 standard library.</p>
</div>
<div class="paragraph">
<p>The aim of this project is to thoroughly define the behavior required of clients. Often the
difficulty with other session-management approaches has been not in implementing code-support for
them, but in not defining rules and behaviour clearly enough.</p>
</div>
<div class="paragraph">
<p>As written above unambiguous rules are created by using RFC 2119 in this document. For the good of
the user, these rules are meant to be followed and are non-negotiable. If an application does not
conform to this specification it should be considered broken. Consistency across applications under
session management is very important for a good user experience.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_client_behavior_under_session_management">1. Client Behavior Under Session Management</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Most graphical applications make available to the user a common set of file operations, typically
presented under a File or Project menu.</p>
</div>
<div class="paragraph">
<p>These are: New, Open, Save, Save As, Close and Quit or Exit.</p>
</div>
<div class="paragraph">
<p>The following sub-sections describe how these options should behave when the application is part of
an NSM session. These rules only apply when session management is active, that is, after the
<code>announce</code> handshake described in the <a href="#_nsm_osc_protocol">NSM OSC Protocol</a> section. In order to provide a
consistent and predictable user experience, it is critically important for applications to adhere
to these guidelines.</p>
</div>
<div class="sect2">
<h3 id="_file_menu">1.1. File Menu</h3>
<div class="sect3">
<h4 id="_new">1.1.1. New</h4>
<div class="paragraph">
<p>This option MAY empty/reset the current file or project (possibly after user confirmation).
It MUST NOT allow the user to create a new project/file in another location.</p>
</div>
</div>
<div class="sect3">
<h4 id="_open">1.1.2. Open</h4>
<div class="paragraph">
<p>This option MUST be disabled.</p>
</div>
<div class="paragraph">
<p>The application MAY elect to implement an option called "Import into Session", which creates a
copy of a file/project which is then saved at the session path provided by NSM.</p>
</div>
</div>
<div class="sect3">
<h4 id="_save">1.1.3. Save</h4>
<div class="paragraph">
<p>This option should behave as normal, saving the current file/project as established by the NSM
<code>open</code> message.</p>
</div>
<div class="paragraph">
<p>This option MUST NOT present the user with a choice of where to save the file.</p>
</div>
</div>
<div class="sect3">
<h4 id="_save_as">1.1.4. Save As</h4>
<div class="paragraph">
<p>This option MUST be disabled.</p>
</div>
<div class="paragraph">
<p>The application MAY elect to implement an option called 'Export from Session', which
creates a copy of the current file/project which is then saved in a user-specified location outside
of the session path provided by NSM.</p>
</div>
</div>
<div class="sect3">
<h4 id="_close_as_distinguished_from_quit_or_exit">1.1.5. Close (as distinguished from Quit or Exit)</h4>
<div class="paragraph">
<p>This option MUST be disabled unless its meaning is to disconnect the application from session
management.</p>
</div>
</div>
<div class="sect3">
<h4 id="_quit_or_exit">1.1.6. Quit or Exit</h4>
<div class="paragraph">
<p>This option MAY behave as normal (possibly asking the user to confirm exiting), or MAY do nothing
to only allow quit from the session-manager control.
When the client supports :optional-gui: this option SHOULD be replaced with hiding the client&#8217;s GUI
so a quit by window manager hides.</p>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_data_storage">1.2. Data Storage</h3>
<div class="sect3">
<h4 id="_internal_files">1.2.1. Internal Files</h4>
<div class="paragraph">
<p>All project specific data created by a client MUST be stored in the per-client storage area
provided by NSM. This includes all recorded audio and MIDI files, snapshots, etc. Only global
configuration items, exports, and renders of the project may be stored elsewhere (wherever the user
specifies).</p>
</div>
</div>
<div class="sect3">
<h4 id="_external_files">1.2.2. External Files</h4>
<div class="paragraph">
<p>Files required by the project but external to it (typically read-only data such as audio samples)
SHOULD be referenced by creating a symbolic link within the assigned session area, and then
referring to the symlink. This allows sessions to be archived and transported simply (e.g. with
"tar -h") by tools that have no knowledge of the project formats of the various clients in the
session. The symlinks thus created should, at the very least, be named after the files they refer
to. Some unique component may be required to prevent collisions.</p>
</div>
</div>
<div class="sect3">
<h4 id="_session_root_and_session_directories">1.2.3. Session Root and Session Directories</h4>
<div class="paragraph">
<p>Client programs MUST NOT handle the following themselves. This section is background-information.</p>
</div>
<div class="paragraph">
<p>NSM follows the <a href="https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html">XDG Base Directory Specifications</a></p>
</div>
<div class="paragraph">
<p>All existing and new sessions are directories below the session-root, which defaults to
<code>$XDG_DATA_HOME/nsm/</code>, which usually results in <code>$HOME/.local/share/nsm/</code>.</p>
</div>
<div class="paragraph">
<p>Each session directory contains a file <code>session.nsm</code> with one client per line <code>name:executable:UID\n</code>
For example:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code>JACKPatch:jackpatch:nBEIQ
jack_mixer:jack_mixer:nTXHV
Carla-Rack:carla-rack:nFAOD</code></pre>
</div>
</div>
<div class="paragraph">
<p><code>nsmd</code> loads and saves this file, client names are their self-reported names.
The file format is final and frozen. Additions or changes SHALL NOT be made.</p>
</div>
<div class="sect4">
<h5 id="_subdirectories_hierarchical_structure">1.2.3.1. Subdirectories / Hierarchical Structure</h5>
<div class="paragraph">
<p>Subdirectories MAY be made to organize sessions into meaningful structures, such as album/track or
composer/genre/piece. For example: <code>Johann Sebastian Bach/Kantaten/Wie schön leuchtet der Morgenstern</code>.
Which results in the same directory structure on disk. Session names can contain any characters that
are supported by the underlying file system, usually UTF-8.</p>
</div>
<div class="paragraph">
<p>Subdirectories are created by either <code>nsmd</code> itself or by the users themselves, through their file
manager or a GUI (while the session is not open).</p>
</div>
<div class="paragraph">
<p>The project_name from <code>/nsm/server/new s:project_name</code> accepts the format <code>a/b/c/d</code>.</p>
</div>
<div class="paragraph">
<p>Any session itself MUST be a "leaf" in this directory tree. A session MUST NOT contain further
session subdirectories: any directory that contains a file <code>session.nsm</code> is the final
element in the hierarchy.</p>
</div>
</div>
<div class="sect4">
<h5 id="_write_protection_for_session_templates">1.2.3.2. Write-Protection for Session Templates</h5>
<div class="paragraph">
<p>Write protection for a whole session directory can either happen by "accident" (files from
another user, a network mount etc.) or on purpose, to protect a session template against accidental
changes. The latter is possible with a recursive <code>chown</code>, <code>chmod</code> or <code>chattr -R +i session-dir</code>.</p>
</div>
<div class="paragraph">
<p>nsmd itself just checks if <code>session.nsm</code> is read-only. In this case it will not send the save
command to it&#8217;s session clients. This does not prevent hypothetical problems when the user
triggers a clients internal save command in a write protected directory. Clients SHOULD handle
their write protected save files themselves.</p>
</div>
<div class="paragraph">
<p>Advanced contraptions, like overlay filesystems or copy-on-write hardlinks to create read-only
sessions without the clients noticing, are out of scope for nsm.</p>
</div>
</div>
<div class="sect4">
<h5 id="_lockfiles">1.2.3.3. Lockfiles</h5>
<div class="paragraph">
<p>Because multiple <code>nsmd</code> can run at the same time we need to prevent accidental write-access to the
same session by different nsm-daemons, and subsequently GUIs.</p>
</div>
<div class="paragraph">
<p>Therefore each currently open session creates a lockfile under <code>$XDG_RUNTIME_DIR/nsm/</code> (usually
<code>/run/user/XXXX/nsm/</code>) that tells <code>nsmd</code> to not open such a locked session. This directory gets
cleaned by the operating system, preventing sessions to stay locked after e.g. a power failure.</p>
</div>
<div class="paragraph">
<p>The lockfile is named after the simple session name combined with a numeric ID for the session
root. It is possible that two <code>nsmd</code> opened two different session roots, both with the same simple
session name, e.g. "my song". Lockfiles are able to distinguish between those and will not prevent
access in this scenario. The numeric ID is a djb2 hash modulo (%) 65521 of the session root directory
(see <code>src/file.cpp</code> function <code>simple_hash()</code>).</p>
</div>
<div class="paragraph">
<p>The lockfile contains, on separate lines:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>The absolute path to the session, including the root-dir, which could be overriden by <code>nsmd --session-root</code>, allowing two sessions of the same basic name in different roots.</p>
</li>
<li>
<p>the OSC URL of the server that runs this session, the same as <code>$NSM_URL</code>.</p>
</li>
<li>
<p>the PID of <code>nsmd</code></p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Example:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code>/home/johann/.local/share/nsm/cantatas/easter1751
osc.udp://myuser.localdomain:11287/
3022</code></pre>
</div>
</div>
</div>
<div class="sect4">
<h5 id="_daemon_discovery">1.2.3.4. Daemon Discovery</h5>
<div class="paragraph">
<p>Each running <code>nsmd</code>, per user, creates a state file under <code>$XDG_RUNTIME_DIR/nsm/d/</code> (usually
<code>/run/user/XXXX/nsm/d/</code>) that can be used to look up running daemons, even if no session is loaded.
The name of the file is <code>nsmd</code> PID and the files contain their daemons osc.udp URL that is
compatible with the --nsm-url parameter of the GUI.</p>
</div>
<div class="paragraph">
<p>This enables you to e.g. start nsmd at boot with a random free port. Server-control programs such
as GUIs can then use this to look for running servers without requiring the user to look up and
input an osc URL manually as command line parameter.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_nsm_osc_protocol">2. NSM OSC Protocol</h2>
<div class="sectionbody">
<div class="paragraph">
<p>All message parameters are REQUIRED. All messages MUST be sent from the same socket as the <code>announce</code>
message, using the <code>lo_send_from</code> method of liblo or its equivalent, as the server uses the return
addresses to distinguish between clients.</p>
</div>
<div class="paragraph">
<p>Clients MUST create thier OSC servers using the same protocol (UDP,TCP) as found in <code>NSM_URL</code>.
<code>nsmd</code> itself is using UDP only.</p>
</div>
<div class="sect2">
<h3 id="_establishing_a_connection">2.1. Establishing a Connection</h3>
<div class="sect3">
<h4 id="_announce">2.1.1. Announce</h4>
<div class="paragraph">
<p>At launch, the client MUST check the environment for the value of <code>NSM_URL</code>. If present, the client
MUST send the following message to the provided address as soon as it is ready to respond to the
<code>/nsm/client/open</code> event:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/nsm/server/announce s:application_name s:capabilities s:executable_name i:api_version_major i:api_version_minor i:pid</code></pre>
</div>
</div>
<div class="paragraph">
<p>If <code>NSM_URL</code> is undefined, invalid, or unreachable, then the client should proceed assuming that
session management is unavailable.</p>
</div>
<div class="paragraph">
<p><code>api_version_major</code> and <code>api_version_minor</code> must be the two parts of the version number of the NSM API
as defined by this document.</p>
</div>
<div class="paragraph">
<p>Note that if the application intends to register JACK clients, <code>application_name</code> MUST be the same as
the name that would normally be passed to <code>jack_client_open</code>. For example, Non-Mixer sends
"Non-Mixer" as its <code>application_name</code>. Applications MUST NOT register their JACK clients until
receiving an <code>open</code> message; the <code>open</code> message will provide a unique client name prefix suitable for
passing to JACK. This is probably the most complex requirement of the NSM API, but it isn&#8217;t
difficult to implement, especially if the application simply wishes to delay its initialization
process briefly while awaiting the <code>announce</code> reply and subsequent <code>open</code> message.</p>
</div>
<div class="paragraph">
<p><code>capabilities</code> MUST be a string containing a colon separated list of the special capabilities the
client possesses. e.g. <code>:dirty:switch:progress:</code></p>
</div>
<div class="paragraph">
<p><code>executable_name</code> MUST be the executable name that the program was launched with. For C programs,
this is simply the value of <code>argv[0]</code>. Note that hardcoding the name of the program here is not the
same as using, as the user may have launched the program from a script with a different name using
exec, or have created a symlink to the program. Getting the correct value in scripting languages
like Python can be more challenging.</p>
</div>
<table class="tableblock frame-all grid-all stripes-even stretch">
<caption class="title">Table 1. Available Client Capabilities</caption>
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">switch</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">client is capable of responding to multiple <code>open</code> messages without restarting</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">dirty</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">client knows when it has unsaved changes</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">progress</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">client can send progress updates during time-consuming operations</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">message</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">client can send textual status updates</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">optional-gui</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">client has an optional GUI</p></td>
</tr>
</tbody>
</table>
</div>
<div class="sect3">
<h4 id="_response">2.1.2. Response</h4>
<div class="paragraph">
<p>The server will respond to the client&#8217;s announce message with the following message:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/reply "/nsm/server/announce" s:message s:name_of_session_manager s:capabilities</code></pre>
</div>
</div>
<div class="paragraph">
<p><code>message</code> is a welcome message.</p>
</div>
<div class="paragraph">
<p>The value of <code>name_of_session_manager</code> will depend on the implementation of the NSM server. It
might say "New Session Manager", or it might say "Non Session Manager" etc. This is for display to
the user.</p>
</div>
<div class="paragraph">
<p><code>capabilities</code> will be a string containing a colon separated list of special server capabilities.</p>
</div>
<div class="paragraph">
<p>Presently, the server <code>capabilities</code> are:</p>
</div>
<table class="tableblock frame-all grid-all stripes-even stretch">
<caption class="title">Table 2. Available Server Capabilities</caption>
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">server-control</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">client-to-server control</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">broadcast</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">server responds to /nsm/server/broadcast message</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">optional-gui</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">server responds to optional-gui messages. This capability is always present and MUST be supported by any server implementation.</p></td>
</tr>
</tbody>
</table>
<div class="paragraph">
<p>A client should not consider itself to be under session management until it receives this response.
For example, the Non applications activate their "SM" blinkers at this time.</p>
</div>
<div class="paragraph">
<p>If there is an error, a reply of the following form will be sent to the client:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/error "/nsm/server/announce" i:error_code s:error_message</code></pre>
</div>
</div>
<div class="paragraph">
<p>The following table defines possible values of <code>error_code</code>:</p>
</div>
<table class="tableblock frame-all grid-all stripes-even stretch">
<caption class="title">Table 3. Response codes</caption>
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Code</th>
<th class="tableblock halign-left valign-top">Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_GENERAL</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">General Error</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_INCOMPATIBLE_API</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Incompatible API version</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_BLACKLISTED</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Client has been blacklisted.</p></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="sect2">
<h3 id="_server_to_client_control_messages">2.2. Server to Client Control Messages</h3>
<div class="paragraph">
<p>Compliant clients MUST accept the client control messages described in this section. All client
control messages REQUIRE a response. Responses MUST be delivered back to the sender (<code>nsmd</code>) from the
same socket used by the client in its <code>announce</code> message (by using <code>lo_send_from</code>) AFTER the action has
been completed or if an error is encountered. The required response is described in the subsection
for each message.</p>
</div>
<div class="paragraph">
<p>If there is an error and the action cannot be completed, then <code>error_code</code> MUST be set to a valid
error code (see <a href="#_error_code_definitions">Error Code Definitions</a>) and <code>message</code> to a string describing the problem
(suitable for display to the user).</p>
</div>
<div class="paragraph">
<p>The reply can take one of the following two forms, where path MUST be the <code>path</code> of the message being
replied to (e.g. "nsm/client/save":</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/reply s:path s:message</code></pre>
</div>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/error s:path i:error_code s:message</code></pre>
</div>
</div>
<div class="sect3">
<h4 id="_quit">2.2.1. Quit</h4>
<div class="paragraph">
<p>There is no message for this. Clients will receive the Unix SIGTERM signal and MUST close cleanly
IMMEDIATELY, without displaying any kind of dialog to the user and regardless of whether or not
unsaved changes would be lost. When a session is closed the application will receive this signal
soon after having responded to a <code>save</code> message.</p>
</div>
</div>
<div class="sect3">
<h4 id="server-to-client-control-messages-open">2.2.2. Open</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/nsm/client/open s:path_to_instance_specific_project s:display_name s:client_id</code></pre>
</div>
</div>
<div class="paragraph">
<p><code>path_to_instance_specific_project</code> is a path name in the form client_name.ID, assigned to the
client for storing its project data. The client MUST choose one of the four strategies below to
save, so that every file in the session can be traced back to a client and, vice versa, a client
name.ID can be used to look up all its files. (For example to clean up the session dir)</p>
</div>
<div class="ulist">
<ul>
<li>
<p>The client has no state and does not save at all</p>
<div class="ulist">
<ul>
<li>
<p>and it MUST NOT misuse e.g. ~/.config to save session specific information e.g. synth-instrument settings</p>
</li>
</ul>
</div>
</li>
<li>
<p>The client may use the path client_name.ID directly, resulting in a file client_name.ID in the session directory</p>
</li>
<li>
<p>The client may append its native file extension (e.g. <code>.json</code>) to the path client_name.ID</p>
</li>
<li>
<p>The client may use the path as directory, creating arbitrary files below, for example recorded .wav.</p>
<div class="ulist">
<ul>
<li>
<p>and it MUST NOT use the client ID below this point. This way the data stays transferable by hand to another client instance (in another session).</p>
</li>
<li>
<p>best case practice is to always use the same file names, for example <code>client_name.ID/savefile.json</code></p>
</li>
</ul>
</div>
</li>
</ul>
</div>
<div class="paragraph">
<p>If a project exists at the path, the client MUST immediately open it.</p>
</div>
<div class="paragraph">
<p>If a project does not exist at the path, then the client MUST immediately create and open a new one
at the specified path or, for clients which hold all their state in memory, store the path for
later use when responding to the <code>save</code> message.</p>
</div>
<div class="paragraph">
<p>No file or directory will be created at the specified path by the server. It is up to the client to
create what it needs.</p>
</div>
<div class="paragraph">
<p>For clients which HAVE NOT specified the <code>:switch:</code> capability, the <code>open</code> message will only be
delivered once, immediately following the <code>announce</code> response.</p>
</div>
<div class="paragraph">
<p>For clients which HAVE specified the <code>:switch:</code> capability, the client MUST immediately switch to the
specified project or create a new one if it doesn&#8217;t exist.</p>
</div>
<div class="paragraph">
<p>Clients which are incapable of switching projects or are prone to crashing upon switching MUST NOT
include <code>:switch:</code> in their capability string.</p>
</div>
<div class="paragraph">
<p>If the user the is allowed to run two or more instances of the application simultaneously
then such an application MUST PRE-PEND the provided <code>client_id</code> string, followed by "/", to any
names it registers with common subsystems (e.g. JACK client names). This ensures that multiple
instances of the same application can be restored in any order without scrambling the JACK
connections or causing other conflicts.</p>
</div>
<div class="paragraph">
<p>The provided <code>client_id</code> will be a concatenation of the value of <code>application_name</code> sent by the
client in its <code>announce</code> message and a unique identifier.</p>
</div>
<div class="paragraph">
<p>Therefore, applications which create single JACK clients can use the value of <code>client_id</code> directly
as their JACK client name.</p>
</div>
<div class="paragraph">
<p>Applications which register multiple JACK clients (e.g. Carla or Non-Mixer) MUST PRE-PEND
<code>client_id</code> value, followed by "/", to the client names they register with JACK and the application
determined part MUST be unique for that (JACK) client.</p>
</div>
<div class="paragraph">
<p>For example, Carla is a plugin-host that loads each plugin as JACK client.
Suitable JACK client names are: <code>carla-jack-multi.nBAF/ZynAddSubFx</code> or <code>carla-jack-multi.nBAF/Helm</code>
Please note that ZynAddSubFx and Helm are <strong>not ports</strong> but clients. Each of them can have any number
of audio and midi ports below them.</p>
</div>
<div class="paragraph">
<p>Note that this means that the application MUST NOT register with JACK (or any
other subsystem requiring unique names) until it receives an <code>open</code> message from NSM. Likewise,
applications with the <code>:switch:</code> capability should close their JACK clients and re-create them with
using the new <code>client_id</code> (renaming JACK-clients is not possible, only ports).</p>
</div>
<div class="paragraph">
<p>A response is REQUIRED as soon as the open operation has been completed. Ongoing progress MAY be
indicated by sending messages to <code>/nsm/client/progress</code>.</p>
</div>
<div class="sect4">
<h5 id="_response_2">2.2.2.1. Response</h5>
<div class="paragraph">
<p>The client MUST respond to the 'open' message with:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/reply "/nsm/client/open" s:message</code></pre>
</div>
</div>
<div class="paragraph">
<p>Or</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/error "/nsm/client/open" i:error_code s:message</code></pre>
</div>
</div>
<table class="tableblock frame-all grid-all stripes-even stretch">
<caption class="title">Table 4. Response codes</caption>
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Code</th>
<th class="tableblock halign-left valign-top">Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">General Error</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_BAD_PROJECT</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">An existing project file was found to be corrupt</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_CREATE_FAILED</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">A new project could not be created</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_UNSAVED_CHANGES</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Unsaved changes would be lost</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_NOT_NOW</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Operation cannot be completed at this time</p></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="sect3">
<h4 id="_save_2">2.2.3. Save</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/nsm/client/save</code></pre>
</div>
</div>
<div class="paragraph">
<p>This message will only be delivered after a previous <code>open</code> message, and may be sent any number of
times within the course of a session (including zero, if the user aborts the session).</p>
</div>
<div class="sect4">
<h5 id="_response_3">2.2.3.1. Response</h5>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/reply "/nsm/client/save" s:message</code></pre>
</div>
</div>
<div class="paragraph">
<p>Or</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/error "/nsm/client/save" i:error_code s:message</code></pre>
</div>
</div>
<table class="tableblock frame-all grid-all stripes-even stretch">
<caption class="title">Table 5. Response codes</caption>
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Code</th>
<th class="tableblock halign-left valign-top">Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">General Error</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_SAVE_FAILED</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Project could not be saved</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_NOT_NOW</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Operation cannot be completed at this time</p></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_server_to_client_informational_messages">2.3. Server to Client Informational Messages</h3>
<div class="sect3">
<h4 id="_session_is_loaded">2.3.1. Session is Loaded</h4>
<div class="paragraph">
<p>Accepting this message is optional. The intent is to signal to clients which may have some
interdependence (say, peer to peer OSC connections) that the session is fully loaded and all their
peers are available. Most clients will not need to act on this message. This message has no meaning
when a session is being built or run; only when it is initially loaded. Clients who intend to act
on this message MUST NOT do so by delaying initialization waiting for it.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/nsm/client/session_is_loaded</code></pre>
</div>
</div>
<div class="paragraph">
<p>This message does not require a response.</p>
</div>
</div>
<div class="sect3">
<h4 id="_show_optional_gui">2.3.2. Show Optional Gui</h4>
<div class="paragraph">
<p>If the client has specified the <code>optional-gui</code> capability, then it may receive this message from the
server when the user wishes to change the visibility state of the GUI. It doesn&#8217;t matter if the
optional GUI is integrated with the program or if it is a separate program \(as is the case with
SooperLooper\). When the GUI is hidden, there should be no window mapped and if the GUI is a
separate program, it should be killed.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/nsm/client/show_optional_gui</code></pre>
</div>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/nsm/client/hide_optional_gui</code></pre>
</div>
</div>
<div class="paragraph">
<p>This message does not require a response.</p>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_client_to_server_informational_messages">2.4. Client to Server Informational Messages</h3>
<div class="sect3">
<h4 id="_optional_gui">2.4.1. Optional GUI</h4>
<div class="paragraph">
<p>If the client has specified the <code>optional-gui</code> capability, then it MUST send this message whenever
the state of visibility of the optional GUI has changed. It also MUST send this message after its
announce message to indicate the initial visibility state of the optional GUI.</p>
</div>
<div class="paragraph">
<p>The client SHOULD always start hidden, if not saved as visible. That implies the first load, after
adding to the session, SHOULD always be hidden.</p>
</div>
<div class="paragraph">
<p>It is the responsibility of the client to remember the visibility state of its GUI across session
loads.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/nsm/client/gui_is_hidden</code></pre>
</div>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/nsm/client/gui_is_shown</code></pre>
</div>
</div>
<div class="paragraph">
<p>No response will be delivered.</p>
</div>
</div>
<div class="sect3">
<h4 id="_progress">2.4.2. Progress</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/nsm/client/progress f:progress</code></pre>
</div>
</div>
<div class="paragraph">
<p>For potentially time-consuming operations, such as <code>save</code> and <code>open</code>, progress updates may be
indicated throughout the duration by sending a floating point value between 0.0 and 1.0, 1.0
indicating completion, to the NSM server.</p>
</div>
<div class="paragraph">
<p>The server will not send a response to these messages, but will relay the information to the user.</p>
</div>
<div class="paragraph">
<p>Note that even when using the <code>progress</code> feature, the final response to the <code>save</code> or <code>open</code>
message is still REQUIRED.</p>
</div>
<div class="paragraph">
<p>Clients which intend to send progress messages MUST include <code>:progress:</code> in their <code>announce</code>
capability string.</p>
</div>
</div>
<div class="sect3">
<h4 id="_dirtiness">2.4.3. Dirtiness</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/nsm/client/is_dirty</code></pre>
</div>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/nsm/client/is_clean</code></pre>
</div>
</div>
<div class="paragraph">
<p>Some clients may be able to inform the server when they have unsaved changes pending. Such clients
may optionally send <code>is_dirty</code> and <code>is_clean</code> messages.</p>
</div>
<div class="paragraph">
<p>Clients which have and use this capability MUST include <code>:dirty:</code> in their <code>announce</code> capability string.</p>
</div>
</div>
<div class="sect3">
<h4 id="_status_messsages">2.4.4. Status Messsages</h4>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/nsm/client/message i:priority s:message</code></pre>
</div>
</div>
<div class="paragraph">
<p>Clients may send miscellaneous status updates to the server for possible display to the user. This
may simply be chatter that is normally written to the console. <code>priority</code> MUST be a number from 0
to 3, 3 being the most important.</p>
</div>
<div class="paragraph">
<p>Clients which have and use this capability MUST include <code>:message:</code> in their <code>announce</code> capability
string.</p>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_error_code_definitions">2.5. Error Code Definitions</h3>
<table class="tableblock frame-all grid-all stripes-even stretch">
<caption class="title">Table 6. Error Code Definitions</caption>
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Symbolic Name</th>
<th class="tableblock halign-left valign-top">Integer Value</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_GENERAL</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">-1</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_INCOMPATIBLE_API</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">-2</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_BLACKLISTED</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">-3</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_LAUNCH_FAILED</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">-4</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_NO_SUCH_FILE</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">-5</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_NO_SESSION_OPEN</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">-6</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_UNSAVED_CHANGES</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">-7</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_NOT_NOW</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">-8</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_BAD_PROJECT</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">-9</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_CREATE_FAILED</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">-10</p></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_client_to_server_control">2.6. Client to Server Control</h3>
<div class="paragraph">
<p>If the server publishes the <code>:server-control:</code> capability, then clients can also initiate action by
the server. For example, a client might implement a 'Save All' option which sends a
<code>/nsm/server/save</code> message to the server, rather than requiring the user to switch to the session
management interface to effect the save.</p>
</div>
</div>
<div class="sect2">
<h3 id="_server_control_api">2.7. Server Control API</h3>
<div class="paragraph">
<p>The session manager not only manages clients via OSC, but it is itself controlled via OSC messages.
The server responds to the following messages.</p>
</div>
<div class="paragraph">
<p>All of the following messages will be responded to, at the sender&#8217;s address, with one of the two
following messages:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/reply s:path s:message</code></pre>
</div>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/error s:path i:error_code s:message</code></pre>
</div>
</div>
<div class="paragraph">
<p>The first parameter of the reply is the path to the message being replied to. The <code>/error</code> reply
includes an integer error code (non-zero indicates error). <code>message</code> will be a description of the
error.</p>
</div>
<div class="paragraph">
<p>The possible errors are:</p>
</div>
<table class="tableblock frame-all grid-all stripes-even stretch">
<caption class="title">Table 7. Responses</caption>
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Code</th>
<th class="tableblock halign-left valign-top">Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_GENERAL</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">General Error</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_LAUNCH_FAILED</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Launch failed</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_NO_SUCH_FILE</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">No such file</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_NO_SESSION</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">No session is open</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">ERR_UNSAVED_CHANGES</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Unsaved changes would be lost</p></td>
</tr>
</tbody>
</table>
<div class="ulist">
<ul>
<li>
<p><code>/nsm/server/add s:executable_name</code></p>
<div class="ulist">
<ul>
<li>
<p>Adds a client to the current session.</p>
</li>
</ul>
</div>
</li>
<li>
<p><code>/nsm/server/save</code></p>
<div class="ulist">
<ul>
<li>
<p>Saves the current session.</p>
</li>
</ul>
</div>
</li>
<li>
<p><code>/nsm/server/open s:project_name</code></p>
<div class="ulist">
<ul>
<li>
<p>Saves the current session and loads a new session.</p>
</li>
</ul>
</div>
</li>
<li>
<p><code>/nsm/server/new s:project_name</code></p>
<div class="ulist">
<ul>
<li>
<p>Saves the current session and creates a new session.</p>
</li>
</ul>
</div>
</li>
<li>
<p><code>/nsm/server/duplicate s:new_project</code></p>
<div class="ulist">
<ul>
<li>
<p>Saves and closes the current session, makes a copy, and opens it.</p>
</li>
</ul>
</div>
</li>
<li>
<p><code>/nsm/server/close</code></p>
<div class="ulist">
<ul>
<li>
<p>Saves and closes the current session.</p>
</li>
</ul>
</div>
</li>
<li>
<p><code>/nsm/server/abort</code></p>
<div class="ulist">
<ul>
<li>
<p>Closes the current session WITHOUT SAVING</p>
</li>
</ul>
</div>
</li>
<li>
<p><code>/nsm/server/quit</code></p>
<div class="ulist">
<ul>
<li>
<p>Saves and closes the current session and terminates the server.</p>
</li>
</ul>
</div>
</li>
<li>
<p><code>/nsm/server/list</code></p>
<div class="ulist">
<ul>
<li>
<p>Lists available projects. One <code>/reply</code> message will be sent for each existing project.</p>
</li>
<li>
<p>Afer listing the last session one final <code>/reply</code> with <code>/nsm/server/list, ""</code> will be send. That is an empty string.</p>
</li>
</ul>
</div>
</li>
</ul>
</div>
<div class="sect3">
<h4 id="_client_to_client_communication">2.7.1. Client to Client Communication</h4>
<div class="paragraph">
<p>If the server includes <code>:broadcast:</code> in its capability string, then clients may send broadcast
messages to each other through the NSM server. Clients may send messages to the server at the path
<code>/nsm/server/broadcast</code>.</p>
</div>
<div class="paragraph">
<p>The format of this message is as follows:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/nsm/server/broadcast s:path [arguments...]</code></pre>
</div>
</div>
<div class="paragraph">
<p>The message will then be relayed to all clients in the session at the path <code>path</code> (with the
arguments shifted by one).</p>
</div>
<div class="paragraph">
<p>For example the message:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/nsm/server/broadcast /tempomap/update "0,120,4/4:12351234,240,4/4"</code></pre>
</div>
</div>
<div class="paragraph">
<p>Would broadcast the following message to all clients in the session (except for the sender), some
of which might respond to the message by updating their own tempo maps.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight nowrap"><code class="language-OSC" data-lang="OSC">/tempomap/update "0,120,4/4:12351234,240,4/4"</code></pre>
</div>
</div>
<div class="paragraph">
<p>The Non programs use this feature to establish peer to peer OSC communication by symbolic names
(client IDs) without having to remember the OSC URLs of peers across sessions.</p>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_api_versions_and_behaviour_changes">3. API Versions and Behaviour Changes</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Here we will document all technical changes or differences in behaviour together with their API and
project version numbers. The term "original" refers to Non Session Manager and "new" refers to New
Session Manager.</p>
</div>
<div class="paragraph">
<p>Version numbers follow <a href="https://semver.org/spec/v2.0.0.html">Semantic Versioning 2.0.0</a></p>
</div>
<div class="listingblock">
<div class="title">Semantic Versioning Scheme</div>
<div class="content">
<pre class="highlight"><code>Given a version number MAJOR.MINOR.PATCH, increment the:

MAJOR version when you make incompatible API changes,
MINOR version when you add functionality in a backwards compatible manner, and
PATCH version when you make backwards compatible bug fixes.</code></pre>
</div>
</div>
<table class="tableblock frame-all grid-all stripes-even stretch">
<caption class="title">Table 8. NSM Version Numbers</caption>
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Subject</th>
<th class="tableblock halign-left valign-top">Version</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Non Session Manager at moment of fork</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">1.2 (June 2020)</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Non Session Manager API</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">1.0 <a href="https://github.com/original-male/non/blob/master/session-manager/src/nsmd.C">NON nsmd.C</a></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">Original API Document</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">1.0 <a href="http://non.tuxfamily.org/nsm/API.html">non.tuxfamily.org/nsm/API.html</a></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">New Session Manager</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">1.6.0</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">New Session Manager API</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">1.1.2 <a href="https://github.com/jackaudio/new-session-manager/blob/master/src/nsmd.cpp">NEW nsmd.cpp</a></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">New API Document</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">1.5.0 <a href="#">Here</a></p></td>
</tr>
</tbody>
</table>
<div class="sect2">
<h3 id="_guidelines">3.1. Guidelines</h3>
<div class="paragraph">
<p>The most important factor in decision making is to keep client compatibility at 100%.
No client will ever receive an unrequested OSC message except those in API 1.0.0.</p>
</div>
<div class="paragraph">
<p>Messages that drastically change existing <code>/nsm/client/</code> or <code>/nsm/server</code> behaviour require an
inrecement to <code>API_VERSION_MAJOR</code>, which we want to avoid.</p>
</div>
<div class="paragraph">
<p><code>nsmd</code> checks if the clients <code>API_VERSION_MAJOR</code> is greater than its own and refuses the client
with <code>ERR_INCOMPATIBLE_API</code>.</p>
</div>
<div class="paragraph">
<p>All changes (that concern client/server behaviour) that increment <code>API_VERSION_MINOR</code> will be
request-only or gated by new capabilities (e.g. <code>:optional-gui:</code>). <code>nsmd</code> will not send any
messages if a capability was not sent by the client in <a href="#_announce"><code>announce</code></a>. This includes
mostly optional features about requesting extra information.</p>
</div>
<div class="paragraph">
<p>New actions for server-control, for example a hypothetical <code>/nsm/server/save_as</code>, which would be
triggered by the client and would only be <strong>answered</strong> by the server ("no unrequested message") will
increment <code>API_VERSION_MINOR</code>.</p>
</div>
<div class="paragraph">
<p>All changes that increment <code>API_VERSION_PATCH</code> will not have any effect on behaviour, except to
fix clear problems, where "problem" is defined by having a different effect than described in this
document, which includes technical problems such as crashes.</p>
</div>
<div class="paragraph">
<p>All messages regarding GUI-communication that start with <code>/nsm/gui/&#8230;&#8203;</code> were undocumented in API
1.0.0 and only used by <code>non-session-manager</code> / <code>nsm-legacy-gui</code>. Until properly documented in this
document this part of the API is considered unstable and may change at any time without notice.
However, when changing already existing messages and behaviour it MAY increment <code>API_VERSION_MINOR</code>
or <code>API_VERSION_PATCH</code>. In that case it will appear in the list below.</p>
</div>
<div class="paragraph">
<p>Last factor of compatibility is that any unknown message sent to <code>nsmd</code> will just print a warning
message to stdout, but will otherwise be ignored. This secures a stable server, even when a client
misbehaves and sends too-new messages outside of announced :capabilites:</p>
</div>
</div>
<div class="sect2">
<h3 id="_changes_in_api_version_1_1_0">3.2. Changes in API Version 1.1.0</h3>
<div class="paragraph">
<p>Rewritten API document without code changes to adapt to existing code or existing client behaviour:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Changed versioning scheme to Semantic Versioning with three positions Major.Minor.Patch</p>
</li>
<li>
<p><a href="#_quit_or_exit">Quit or Exit</a> SHOULD hide instead of exiting when :optional-gui: is supported and MAY not
act on the quit through menu otherwise.</p>
</li>
<li>
<p><a href="#server-to-client-control-messages-open">Open</a>: Make clear that there are only certain
possibilities for save paths. We added MUST because the rule was just implied before.</p>
</li>
<li>
<p><a href="#server-to-client-control-messages-open">Open</a>: Make clear that the delimiter for
multi-jack clients is "/".</p>
</li>
<li>
<p><a href="#_optional_gui">Optional GUI</a> SHOULD start hidden, always after a fresh add to the session. After that saving
the visibility state may override it for next time.</p>
</li>
<li>
<p><a href="#_progress">Progress</a> MUST be announced in :capabilities: . Before there was a lower case "should",
which means nothing. Parallel-examples in the specs cleary say that supporting optional features must be announced first.</p>
<div class="ulist">
<ul>
<li>
<p>Same for <a href="#_dirtiness">Dirtiness</a> and <a href="#_status_messsages">Status Messsages</a>.</p>
</li>
</ul>
</div>
</li>
<li>
<p><a href="#_status_messsages">Status Messsages</a> have priority numbers between 0 and 3, so they MUST send that.
It was never an arbitrary value.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Code changes:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><a href="#_server_control_api">Server Control API</a>: <code>/nsm/server/list</code> chain of single OSC messages, one for each session,
is now finalized with sending and empty string "" as session name. Previously this was just
a symbolically irrelevant console message <code>"Done."</code></p>
</li>
<li>
<p>Replies to <code>/nsm/server/save</code> etc. will now be sent back to the sender and not falsely to the last
client who replied to <code>/nsm/client/save</code>.  This alone would only require API_VERSION_PATCH
increment, but we are already incrementing minor.</p>
</li>
<li>
<p><a href="#_server_control_api">Server Control API</a>: <code>/nsm/server/add</code> was replying with an undocumented error code on success.
Instead, as this document always specificed, it now sends <code>"/reply", path, "Launched."</code>.
Again, this would have been just API_VERSION_PATCH on its own.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Undocumented (Unstable) <code>/nsm/gui</code> protocol</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Send client status after a GUI attaches to running server. This
was not happening before, but it was the intention. It was just broken in nsmd.cpp. This alone
would only require API_VERSION_PATCH increment, but we are already incrementing minor.</p>
</li>
<li>
<p>Send label "launch error!" when a program is added (or loaded) that
does not exist in $PATH. This requires no adaptation of any client, server or GUI because labels
are arbitrary already and this is not meant for automatic parsing, but as user information.</p>
</li>
<li>
<p><code>/nsm/gui/session/name</code> will now always send the same parameter format, regardless of how the session was opened:
simple-session-name, relative session path with subdirs below session-root.</p>
</li>
<li>
<p>When a GUI announces itself to nsmd it will receive the absolute path to the session directory
through the message <code>/nsm/gui/session/root</code>. This is not a new addition but was already in
non-session-manager git.</p>
</li>
</ul>
</div>
</div>
<div class="sect2">
<h3 id="_changes_in_api_version_1_1_1">3.3. Changes in API Version 1.1.1</h3>
<div class="ulist">
<ul>
<li>
<p>Server-capability :optional-gui: is now mandatory for SERVER implementations. Reasoning:
This is an  important core feature of NSM and thus will be treated as such by guaranteeing it to exist.
After looking at all currently known clients and server-implementations it turns out that all servers
support :optional-gui: and the vast majority of clients not only support it, but actually assume it
and do <em>not</em> test for the server capability, as it was written in this document.
There are now two choices: Adjust this document to the (good) reality or consider all clients broken.
Summary: We consider this API document wrong and therefore fix it, thus increasing API version
patch-level from 1.1.0 to 1.1.1</p>
</li>
<li>
<p>Add API-section "Subdirectories / Hierarchical Structure" that explains the session directory.
This behaviour was already the case for nsm-legacy-gui and nsmd 1.5.0 was patched to adhere to this
behaviour more strictly as well, removing false session entries in 3rd party clients such as Agordejo.</p>
</li>
</ul>
</div>
</div>
<div class="sect2">
<h3 id="_changes_in_api_version_1_1_2">3.4. Changes in API Version 1.1.2</h3>
<div class="ulist">
<ul>
<li>
<p>nsmd now follows the XDG Base Directory Specifications for it&#8217;s session root and lock files. This
if of no consequence to clients but required documentation nevertheless, which was described as
"background information" in the chapters for lock files and daemon disovery.</p>
</li>
<li>
<p>nsmd now gracefully handles read-only <code>session.nsm</code> files. This theoretically enables read-only
sessions and session-templates.  It is included in the patch-level because this was marked as a
long-standing <code>FIXME</code> in the code by the original author. Or in other words: just a bug fix.</p>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div id="footer">
<div id="footer-text">
Version API 1.1.2<br>
Last updated 2022-04-15 00:54:20 +0200
</div>
</div>
</body>
</html>07070100000009000081A40000000000000000000000016258A65C000083DC000000000000000000000000000000000000003F00000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/index.html<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 2.0.17">
<meta name="author" content="Nils Hilbricht">
<title>New Session Manager Documentation</title>
<style>
/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
/* Uncomment the following line when using as a custom stylesheet */
/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
html{font-family:sans-serif;-webkit-text-size-adjust:100%}
a{background:none}
a:focus{outline:thin dotted}
a:active,a:hover{outline:0}
h1{font-size:2em;margin:.67em 0}
b,strong{font-weight:bold}
abbr{font-size:.9em}
abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
dfn{font-style:italic}
hr{height:0}
mark{background:#ff0;color:#000}
code,kbd,pre,samp{font-family:monospace;font-size:1em}
pre{white-space:pre-wrap}
q{quotes:"\201C" "\201D" "\2018" "\2019"}
small{font-size:80%}
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sup{top:-.5em}
sub{bottom:-.25em}
img{border:0}
svg:not(:root){overflow:hidden}
figure{margin:0}
audio,video{display:inline-block}
audio:not([controls]){display:none;height:0}
fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
legend{border:0;padding:0}
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
button,input{line-height:normal}
button,select{text-transform:none}
button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
button[disabled],html input[disabled]{cursor:default}
input[type=checkbox],input[type=radio]{padding:0}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
textarea{overflow:auto;vertical-align:top}
table{border-collapse:collapse;border-spacing:0}
*,::before,::after{box-sizing:border-box}
html,body{font-size:100%}
body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
a:hover{cursor:pointer}
img,object,embed{max-width:100%;height:auto}
object,embed{height:100%}
img{-ms-interpolation-mode:bicubic}
.left{float:left!important}
.right{float:right!important}
.text-left{text-align:left!important}
.text-right{text-align:right!important}
.text-center{text-align:center!important}
.text-justify{text-align:justify!important}
.hide{display:none}
img,object,svg{display:inline-block;vertical-align:middle}
textarea{height:auto;min-height:50px}
select{width:100%}
.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
a{color:#2156a5;text-decoration:underline;line-height:inherit}
a:hover,a:focus{color:#1d4b8f}
a img{border:0}
p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
p aside{font-size:.875em;line-height:1.35;font-style:italic}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
h1{font-size:2.125em}
h2{font-size:1.6875em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
h4,h5{font-size:1.125em}
h6{font-size:1em}
hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
em,i{font-style:italic;line-height:inherit}
strong,b{font-weight:bold;line-height:inherit}
small{font-size:60%;line-height:inherit}
code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
ul,ol{margin-left:1.5em}
ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}
ul.square{list-style-type:square}
ul.circle{list-style-type:circle}
ul.disc{list-style-type:disc}
ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
dl dt{margin-bottom:.3125em;font-weight:bold}
dl dd{margin-bottom:1.25em}
blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
h1{font-size:2.75em}
h2{font-size:2.3125em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
h4{font-size:1.4375em}}
table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
table thead,table tfoot{background:#f7f8f7}
table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
table tr.even,table tr.alt{background:#f8f8f7}
table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
.center{margin-left:auto;margin-right:auto}
.stretch{width:100%}
.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
.clearfix::after,.float-group::after{clear:both}
:not(pre).nobreak{word-wrap:normal}
:not(pre).nowrap{white-space:nowrap}
:not(pre).pre-wrap{white-space:pre-wrap}
:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
pre>code{display:block}
pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
em em{font-style:normal}
strong strong{font-weight:400}
.keyseq{color:rgba(51,51,51,.8)}
kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
.keyseq kbd:first-child{margin-left:0}
.keyseq kbd:last-child{margin-right:0}
.menuseq,.menuref{color:#000}
.menuseq b:not(.caret),.menuref{font-weight:inherit}
.menuseq{word-spacing:-.02em}
.menuseq b.caret{font-size:1.25em;line-height:.8}
.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
b.button::before{content:"[";padding:0 3px 0 2px}
b.button::after{content:"]";padding:0 2px 0 3px}
p a>code:hover{color:rgba(0,0,0,.9)}
#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
#content{margin-top:1.25em}
#content::before{content:none}
#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
#header .details span:first-child{margin-left:-.125em}
#header .details span.email a{color:rgba(0,0,0,.85)}
#header .details br{display:none}
#header .details br+span::before{content:"\00a0\2013\00a0"}
#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
#header .details br+span#revremark::before{content:"\00a0|\00a0"}
#header #revnumber{text-transform:capitalize}
#header #revnumber::after{content:"\00a0"}
#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
#toc>ul{margin-left:.125em}
#toc ul.sectlevel0>li>a{font-style:italic}
#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
#toc li{line-height:1.3334;margin-top:.3334em}
#toc a{text-decoration:none}
#toc a:active{text-decoration:underline}
#toctitle{color:#7a2518;font-size:1.2em}
@media screen and (min-width:768px){#toctitle{font-size:1.375em}
body.toc2{padding-left:15em;padding-right:0}
#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
#toc.toc2>ul{font-size:.9em;margin-bottom:0}
#toc.toc2 ul ul{margin-left:0;padding-left:1em}
#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
body.toc2.toc-right{padding-left:0;padding-right:15em}
body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
#toc.toc2{width:20em}
#toc.toc2 #toctitle{font-size:1.375em}
#toc.toc2>ul{font-size:.95em}
#toc.toc2 ul ul{padding-left:1.25em}
body.toc2.toc-right{padding-left:0;padding-right:20em}}
#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
#content #toc>:first-child{margin-top:0}
#content #toc>:last-child{margin-bottom:0}
#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
#content{margin-bottom:.625em}
.sect1{padding-bottom:.625em}
@media screen and (min-width:768px){#content{margin-bottom:1.25em}
.sect1{padding-bottom:1.25em}}
.sect1:last-child{padding-bottom:0}
.sect1+.sect1{border-top:1px solid #e7e7e9}
#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
details{margin-left:1.25rem}
details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
details>summary::-webkit-details-marker{display:none}
details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
.admonitionblock>table td.icon{text-align:center;width:80px}
.admonitionblock>table td.icon img{max-width:none}
.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
.exampleblock>.content>:first-child{margin-top:0}
.exampleblock>.content>:last-child{margin-bottom:0}
.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
.sidebarblock>:first-child{margin-top:0}
.sidebarblock>:last-child{margin-bottom:0}
.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
.listingblock>.content{position:relative}
.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
.listingblock:hover code[data-lang]::before{display:block}
.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
.listingblock pre.highlightjs{padding:0}
.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
.listingblock pre.prettyprint{border-width:0}
.prettyprint{background:#f7f7f8}
pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
pre.prettyprint li code[data-lang]::before{opacity:1}
pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
table.linenotable td.code{padding-left:.75em}
table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
pre.pygments span.linenos{display:inline-block;margin-right:.75em}
.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
.quoteblock blockquote{margin:0;padding:0;border:0}
.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
.verseblock{margin:0 1em 1.25em}
.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
.verseblock pre strong{font-weight:400}
.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
.quoteblock .attribution br,.verseblock .attribution br{display:none}
.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
.quoteblock.abstract{margin:0 1em 1.25em;display:block}
.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
p.tableblock:last-child{margin-bottom:0}
td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
td.tableblock>.content>:last-child{margin-bottom:-1.25em}
table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
table.grid-all>*>tr>*{border-width:1px}
table.grid-cols>*>tr>*{border-width:0 1px}
table.grid-rows>*>tr>*{border-width:1px 0}
table.frame-all{border-width:1px}
table.frame-ends{border-width:1px 0}
table.frame-sides{border-width:0 1px}
table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
th.halign-left,td.halign-left{text-align:left}
th.halign-right,td.halign-right{text-align:right}
th.halign-center,td.halign-center{text-align:center}
th.valign-top,td.valign-top{vertical-align:top}
th.valign-bottom,td.valign-bottom{vertical-align:bottom}
th.valign-middle,td.valign-middle{vertical-align:middle}
table thead th,table tfoot th{font-weight:bold}
tbody tr th{background:#f7f8f7}
tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
p.tableblock>code:only-child{background:none;padding:0}
p.tableblock{font-size:1em}
ol{margin-left:1.75em}
ul li ol{margin-left:1.5em}
dl dd{margin-left:1.125em}
dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
ul.unstyled,ol.unstyled{margin-left:0}
li>p:empty:only-child::before{content:"";display:inline-block}
ul.checklist>li>p:first-child{margin-left:-1em}
ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
ul.inline>li{margin-left:1.25em}
.unstyled dl dt{font-weight:400;font-style:normal}
ol.arabic{list-style-type:decimal}
ol.decimal{list-style-type:decimal-leading-zero}
ol.loweralpha{list-style-type:lower-alpha}
ol.upperalpha{list-style-type:upper-alpha}
ol.lowerroman{list-style-type:lower-roman}
ol.upperroman{list-style-type:upper-roman}
ol.lowergreek{list-style-type:lower-greek}
.hdlist>table,.colist>table{border:0;background:none}
.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
td.hdlist1{font-weight:bold;padding-bottom:1.25em}
td.hdlist2{word-wrap:anywhere}
.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
.colist td:not([class]):first-child img{max-width:none}
.colist td:not([class]):last-child{padding:.25em 0}
.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
.imageblock.left{margin:.25em .625em 1.25em 0}
.imageblock.right{margin:.25em 0 1.25em .625em}
.imageblock>.title{margin-bottom:0}
.imageblock.thumb,.imageblock.th{border-width:6px}
.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
.image.left{margin-right:.625em}
.image.right{margin-left:.625em}
a.image{text-decoration:none;display:inline-block}
a.image object{pointer-events:none}
sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
sup.footnote a,sup.footnoteref a{text-decoration:none}
sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
#footnotes .footnote:last-of-type{margin-bottom:0}
#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
div.unbreakable{page-break-inside:avoid}
.big{font-size:larger}
.small{font-size:smaller}
.underline{text-decoration:underline}
.overline{text-decoration:overline}
.line-through{text-decoration:line-through}
.aqua{color:#00bfbf}
.aqua-background{background:#00fafa}
.black{color:#000}
.black-background{background:#000}
.blue{color:#0000bf}
.blue-background{background:#0000fa}
.fuchsia{color:#bf00bf}
.fuchsia-background{background:#fa00fa}
.gray{color:#606060}
.gray-background{background:#7d7d7d}
.green{color:#006000}
.green-background{background:#007d00}
.lime{color:#00bf00}
.lime-background{background:#00fa00}
.maroon{color:#600000}
.maroon-background{background:#7d0000}
.navy{color:#000060}
.navy-background{background:#00007d}
.olive{color:#606000}
.olive-background{background:#7d7d00}
.purple{color:#600060}
.purple-background{background:#7d007d}
.red{color:#bf0000}
.red-background{background:#fa0000}
.silver{color:#909090}
.silver-background{background:#bcbcbc}
.teal{color:#006060}
.teal-background{background:#007d7d}
.white{color:#bfbfbf}
.white-background{background:#fafafa}
.yellow{color:#bfbf00}
.yellow-background{background:#fafa00}
span.icon>.fa{cursor:default}
a span.icon>.fa{cursor:inherit}
.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
.conum[data-value] *{color:#fff!important}
.conum[data-value]+b{display:none}
.conum[data-value]::after{content:attr(data-value)}
pre .conum[data-value]{position:relative;top:-.125em}
b.conum *{color:inherit!important}
.conum:not([data-value]):empty{display:none}
dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
p,blockquote,dt,td.content,span.alt,summary{font-size:1.0625rem}
p{margin-bottom:1.25rem}
.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
.print-only{display:none!important}
@page{margin:1.25cm .75cm}
@media print{*{box-shadow:none!important;text-shadow:none!important}
html{font-size:80%}
a{color:inherit!important;text-decoration:underline!important}
a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
abbr[title]{border-bottom:1px dotted}
abbr[title]::after{content:" (" attr(title) ")"}
pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
thead{display:table-header-group}
svg{max-width:100%}
p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
#header,#content,#footnotes,#footer{max-width:none}
#toc,.sidebarblock,.exampleblock>.content{background:none!important}
#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
body.book #header{text-align:center}
body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
body.book #header .details{border:0!important;display:block;padding:0!important}
body.book #header .details span:first-child{margin-left:0!important}
body.book #header .details br{display:block}
body.book #header .details br+span::before{content:none!important}
body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
.listingblock code[data-lang]::before{display:block}
#footer{padding:0 .9375em}
.hide-on-print{display:none!important}
.print-only{display:block!important}
.hide-for-print{display:none!important}
.show-for-print{display:inherit!important}}
@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
.sect1{padding:0!important}
.sect1+.sect1{border:0}
#footer{background:none}
#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
</style>
</head>
<body class="article">
<div id="header">
<h1>New Session Manager Documentation</h1>
<div class="details">
<span id="author" class="author">Nils Hilbricht</span><br>
<span id="revnumber">version 1.6.0</span>
</div>
</div>
<div id="content">
<div id="preamble">
<div class="sectionbody">
<div class="ulist">
<ul>
<li>
<p><a href="https://github.com/jackaudio/new-session-manager">Sourcecode</a></p>
</li>
<li>
<p><a href="https://github.com/jackaudio/new-session-manager/issues">Bug and Issue Tracker</a></p>
</li>
<li>
<p><a href="api/index.html">API</a> document that describes all OSC Messages</p>
</li>
<li>
<p><a href="http://non.tuxfamily.org/session-manager/doc/MANUAL.html">Legacy-GUI Manual</a>. The original Non-Session-Manager GUI manual is still valid.</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_introduction">Introduction</h2>
<div class="sectionbody">
<div class="paragraph">
<p>New Session Manager (NSM) is a tool to assist music production by grouping standalone programs into
sessions. Your workflow becomes easy to manage, robust and fast by leveraging the full potential of
cooperative applications.</p>
</div>
<div class="paragraph">
<p>NSM continues to be free in every sense of the word: free of cost, free to share and use, free of
spyware or ads, free-and-open-source.</p>
</div>
<div class="paragraph">
<p>You can create a session, or project, add programs to it and then use commands to save, start/stop,
hide/show all programs at once, or individually. At a later date you can then re-open the session
and continue where you left off.</p>
</div>
<div class="paragraph">
<p>All files belonging to the session will be saved in the same directory.</p>
</div>
<div class="paragraph">
<p>If you are a user (and not a programmer or packager) everything you need is to install NSM
through your distributions package manager and, highly recommended, Agordejo as a GUI (see below).</p>
</div>
<div class="paragraph">
<p>To learn NSM you don&#8217;t need to know the background information from our documentation, which
is aimed at developers that want to implement NSM support in their programs. Learn the GUI,
not the server and protocol.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_bullet_points">Bullet Points</h2>
<div class="sectionbody">
<div class="ulist">
<ul>
<li>
<p>Drop-In replacement for the non-session-manager daemon nsmd and tools (e.g. jackpatch)</p>
</li>
<li>
<p>Simple and hassle-free build system to make packaging easy</p>
</li>
<li>
<p>Possibility to react to sensible bug fixes that would not have been integrated original nsmd</p>
</li>
<li>
<p>Stay upwards and downwards compatible with original nsmd</p>
</li>
<li>
<p>Conservative and hesitant in regards to new features and behaviour-changes, but possible in principle</p>
</li>
<li>
<p>Keep the session-manager separate from the other NON* tools Mixer, Sequencer and Timeline.</p>
</li>
<li>
<p>Protect nsmd from vanishing from the internet one day.</p>
</li>
<li>
<p>The goal is to become the de-facto standard music session manager for Linux distributions</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_user_interface">User Interface</h2>
<div class="sectionbody">
<div class="paragraph">
<p>It is highly recommended to use Agordejo ( <a href="https://www.laborejo.org/agordejo/" class="bare">https://www.laborejo.org/agordejo/</a> ) as graphical
user interface. In fact, if you install Agordejo in your distribution it will install NSM as
dependency and you don&#8217;t need to do anything yourself with this software package.</p>
</div>
<div class="paragraph">
<p>This repository also contains the legacy FLTK interface simply called <code>nsm-legacy-gui</code>,
symlinked to <code>non-session-manager</code> for backwards compatibility. (e.g. autostart scripts etc.)</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_supported_clients">Supported Clients</h2>
<div class="sectionbody">
<div class="paragraph">
<p>While NSM can start and stop any program it only becomes convenient if clients specifically
implement support. This enables saving and hiding the GUI, amongst other features.
Documentation and tutorials for software-developers will be added at a later date.</p>
</div>
</div>
</div>
</div>
<div id="footer">
<div id="footer-text">
Version 1.6.0<br>
Last updated 2022-04-15 00:54:20 +0200
</div>
</div>
</body>
</html>0707010000000A000041ED0000000000000000000000036258A65C00000000000000000000000000000000000000000000003800000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src0707010000000B000081A40000000000000000000000016258A65C00004EEE000000000000000000000000000000000000004000000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/LICENSEfrom: https://creativecommons.org/licenses/by-sa/4.0/legalcode.txt

Attribution-ShareAlike 4.0 International

=======================================================================

Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.

Using Creative Commons Public Licenses

Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.

     Considerations for licensors: Our public licenses are
     intended for use by those authorized to give the public
     permission to use material in ways otherwise restricted by
     copyright and certain other rights. Our licenses are
     irrevocable. Licensors should read and understand the terms
     and conditions of the license they choose before applying it.
     Licensors should also secure all rights necessary before
     applying our licenses so that the public can reuse the
     material as expected. Licensors should clearly mark any
     material not subject to the license. This includes other CC-
     licensed material, or material used under an exception or
     limitation to copyright. More considerations for licensors:
    wiki.creativecommons.org/Considerations_for_licensors

     Considerations for the public: By using one of our public
     licenses, a licensor grants the public permission to use the
     licensed material under specified terms and conditions. If
     the licensor's permission is not necessary for any reason--for
     example, because of any applicable exception or limitation to
     copyright--then that use is not regulated by the license. Our
     licenses grant only permissions under copyright and certain
     other rights that a licensor has authority to grant. Use of
     the licensed material may still be restricted for other
     reasons, including because others have copyright or other
     rights in the material. A licensor may make special requests,
     such as asking that all changes be marked or described.
     Although not required by our licenses, you are encouraged to
     respect those requests where reasonable. More considerations
     for the public:
    wiki.creativecommons.org/Considerations_for_licensees

=======================================================================

Creative Commons Attribution-ShareAlike 4.0 International Public
License

By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution-ShareAlike 4.0 International Public License ("Public
License"). To the extent this Public License may be interpreted as a
contract, You are granted the Licensed Rights in consideration of Your
acceptance of these terms and conditions, and the Licensor grants You
such rights in consideration of benefits the Licensor receives from
making the Licensed Material available under these terms and
conditions.


Section 1 -- Definitions.

  a. Adapted Material means material subject to Copyright and Similar
     Rights that is derived from or based upon the Licensed Material
     and in which the Licensed Material is translated, altered,
     arranged, transformed, or otherwise modified in a manner requiring
     permission under the Copyright and Similar Rights held by the
     Licensor. For purposes of this Public License, where the Licensed
     Material is a musical work, performance, or sound recording,
     Adapted Material is always produced where the Licensed Material is
     synched in timed relation with a moving image.

  b. Adapter's License means the license You apply to Your Copyright
     and Similar Rights in Your contributions to Adapted Material in
     accordance with the terms and conditions of this Public License.

  c. BY-SA Compatible License means a license listed at
     creativecommons.org/compatiblelicenses, approved by Creative
     Commons as essentially the equivalent of this Public License.

  d. Copyright and Similar Rights means copyright and/or similar rights
     closely related to copyright including, without limitation,
     performance, broadcast, sound recording, and Sui Generis Database
     Rights, without regard to how the rights are labeled or
     categorized. For purposes of this Public License, the rights
     specified in Section 2(b)(1)-(2) are not Copyright and Similar
     Rights.

  e. Effective Technological Measures means those measures that, in the
     absence of proper authority, may not be circumvented under laws
     fulfilling obligations under Article 11 of the WIPO Copyright
     Treaty adopted on December 20, 1996, and/or similar international
     agreements.

  f. Exceptions and Limitations means fair use, fair dealing, and/or
     any other exception or limitation to Copyright and Similar Rights
     that applies to Your use of the Licensed Material.

  g. License Elements means the license attributes listed in the name
     of a Creative Commons Public License. The License Elements of this
     Public License are Attribution and ShareAlike.

  h. Licensed Material means the artistic or literary work, database,
     or other material to which the Licensor applied this Public
     License.

  i. Licensed Rights means the rights granted to You subject to the
     terms and conditions of this Public License, which are limited to
     all Copyright and Similar Rights that apply to Your use of the
     Licensed Material and that the Licensor has authority to license.

  j. Licensor means the individual(s) or entity(ies) granting rights
     under this Public License.

  k. Share means to provide material to the public by any means or
     process that requires permission under the Licensed Rights, such
     as reproduction, public display, public performance, distribution,
     dissemination, communication, or importation, and to make material
     available to the public including in ways that members of the
     public may access the material from a place and at a time
     individually chosen by them.

  l. Sui Generis Database Rights means rights other than copyright
     resulting from Directive 96/9/EC of the European Parliament and of
     the Council of 11 March 1996 on the legal protection of databases,
     as amended and/or succeeded, as well as other essentially
     equivalent rights anywhere in the world.

  m. You means the individual or entity exercising the Licensed Rights
     under this Public License. Your has a corresponding meaning.


Section 2 -- Scope.

  a. License grant.

       1. Subject to the terms and conditions of this Public License,
          the Licensor hereby grants You a worldwide, royalty-free,
          non-sublicensable, non-exclusive, irrevocable license to
          exercise the Licensed Rights in the Licensed Material to:

            a. reproduce and Share the Licensed Material, in whole or
               in part; and

            b. produce, reproduce, and Share Adapted Material.

       2. Exceptions and Limitations. For the avoidance of doubt, where
          Exceptions and Limitations apply to Your use, this Public
          License does not apply, and You do not need to comply with
          its terms and conditions.

       3. Term. The term of this Public License is specified in Section
          6(a).

       4. Media and formats; technical modifications allowed. The
          Licensor authorizes You to exercise the Licensed Rights in
          all media and formats whether now known or hereafter created,
          and to make technical modifications necessary to do so. The
          Licensor waives and/or agrees not to assert any right or
          authority to forbid You from making technical modifications
          necessary to exercise the Licensed Rights, including
          technical modifications necessary to circumvent Effective
          Technological Measures. For purposes of this Public License,
          simply making modifications authorized by this Section 2(a)
          (4) never produces Adapted Material.

       5. Downstream recipients.

            a. Offer from the Licensor -- Licensed Material. Every
               recipient of the Licensed Material automatically
               receives an offer from the Licensor to exercise the
               Licensed Rights under the terms and conditions of this
               Public License.

            b. Additional offer from the Licensor -- Adapted Material.
               Every recipient of Adapted Material from You
               automatically receives an offer from the Licensor to
               exercise the Licensed Rights in the Adapted Material
               under the conditions of the Adapter's License You apply.

            c. No downstream restrictions. You may not offer or impose
               any additional or different terms or conditions on, or
               apply any Effective Technological Measures to, the
               Licensed Material if doing so restricts exercise of the
               Licensed Rights by any recipient of the Licensed
               Material.

       6. No endorsement. Nothing in this Public License constitutes or
          may be construed as permission to assert or imply that You
          are, or that Your use of the Licensed Material is, connected
          with, or sponsored, endorsed, or granted official status by,
          the Licensor or others designated to receive attribution as
          provided in Section 3(a)(1)(A)(i).

  b. Other rights.

       1. Moral rights, such as the right of integrity, are not
          licensed under this Public License, nor are publicity,
          privacy, and/or other similar personality rights; however, to
          the extent possible, the Licensor waives and/or agrees not to
          assert any such rights held by the Licensor to the limited
          extent necessary to allow You to exercise the Licensed
          Rights, but not otherwise.

       2. Patent and trademark rights are not licensed under this
          Public License.

       3. To the extent possible, the Licensor waives any right to
          collect royalties from You for the exercise of the Licensed
          Rights, whether directly or through a collecting society
          under any voluntary or waivable statutory or compulsory
          licensing scheme. In all other cases the Licensor expressly
          reserves any right to collect such royalties.


Section 3 -- License Conditions.

Your exercise of the Licensed Rights is expressly made subject to the
following conditions.

  a. Attribution.

       1. If You Share the Licensed Material (including in modified
          form), You must:

            a. retain the following if it is supplied by the Licensor
               with the Licensed Material:

                 i. identification of the creator(s) of the Licensed
                    Material and any others designated to receive
                    attribution, in any reasonable manner requested by
                    the Licensor (including by pseudonym if
                    designated);

                ii. a copyright notice;

               iii. a notice that refers to this Public License;

                iv. a notice that refers to the disclaimer of
                    warranties;

                 v. a URI or hyperlink to the Licensed Material to the
                    extent reasonably practicable;

            b. indicate if You modified the Licensed Material and
               retain an indication of any previous modifications; and

            c. indicate the Licensed Material is licensed under this
               Public License, and include the text of, or the URI or
               hyperlink to, this Public License.

       2. You may satisfy the conditions in Section 3(a)(1) in any
          reasonable manner based on the medium, means, and context in
          which You Share the Licensed Material. For example, it may be
          reasonable to satisfy the conditions by providing a URI or
          hyperlink to a resource that includes the required
          information.

       3. If requested by the Licensor, You must remove any of the
          information required by Section 3(a)(1)(A) to the extent
          reasonably practicable.

  b. ShareAlike.

     In addition to the conditions in Section 3(a), if You Share
     Adapted Material You produce, the following conditions also apply.

       1. The Adapter's License You apply must be a Creative Commons
          license with the same License Elements, this version or
          later, or a BY-SA Compatible License.

       2. You must include the text of, or the URI or hyperlink to, the
          Adapter's License You apply. You may satisfy this condition
          in any reasonable manner based on the medium, means, and
          context in which You Share Adapted Material.

       3. You may not offer or impose any additional or different terms
          or conditions on, or apply any Effective Technological
          Measures to, Adapted Material that restrict exercise of the
          rights granted under the Adapter's License You apply.


Section 4 -- Sui Generis Database Rights.

Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:

  a. for the avoidance of doubt, Section 2(a)(1) grants You the right
     to extract, reuse, reproduce, and Share all or a substantial
     portion of the contents of the database;

  b. if You include all or a substantial portion of the database
     contents in a database in which You have Sui Generis Database
     Rights, then the database in which You have Sui Generis Database
     Rights (but not its individual contents) is Adapted Material,

     including for purposes of Section 3(b); and
  c. You must comply with the conditions in Section 3(a) if You Share
     all or a substantial portion of the contents of the database.

For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.


Section 5 -- Disclaimer of Warranties and Limitation of Liability.

  a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
     EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
     AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
     ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
     IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
     WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
     PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
     ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
     KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
     ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.

  b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
     TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
     NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
     INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
     COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
     USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
     ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
     DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
     IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.

  c. The disclaimer of warranties and limitation of liability provided
     above shall be interpreted in a manner that, to the extent
     possible, most closely approximates an absolute disclaimer and
     waiver of all liability.


Section 6 -- Term and Termination.

  a. This Public License applies for the term of the Copyright and
     Similar Rights licensed here. However, if You fail to comply with
     this Public License, then Your rights under this Public License
     terminate automatically.

  b. Where Your right to use the Licensed Material has terminated under
     Section 6(a), it reinstates:

       1. automatically as of the date the violation is cured, provided
          it is cured within 30 days of Your discovery of the
          violation; or

       2. upon express reinstatement by the Licensor.

     For the avoidance of doubt, this Section 6(b) does not affect any
     right the Licensor may have to seek remedies for Your violations
     of this Public License.

  c. For the avoidance of doubt, the Licensor may also offer the
     Licensed Material under separate terms or conditions or stop
     distributing the Licensed Material at any time; however, doing so
     will not terminate this Public License.

  d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
     License.


Section 7 -- Other Terms and Conditions.

  a. The Licensor shall not be bound by any additional or different
     terms or conditions communicated by You unless expressly agreed.

  b. Any arrangements, understandings, or agreements regarding the
     Licensed Material not stated herein are separate from and
     independent of the terms and conditions of this Public License.


Section 8 -- Interpretation.

  a. For the avoidance of doubt, this Public License does not, and
     shall not be interpreted to, reduce, limit, restrict, or impose
     conditions on any use of the Licensed Material that could lawfully
     be made without permission under this Public License.

  b. To the extent possible, if any provision of this Public License is
     deemed unenforceable, it shall be automatically reformed to the
     minimum extent necessary to make it enforceable. If the provision
     cannot be reformed, it shall be severed from this Public License
     without affecting the enforceability of the remaining terms and
     conditions.

  c. No term or condition of this Public License will be waived and no
     failure to comply consented to unless expressly agreed to by the
     Licensor.

  d. Nothing in this Public License constitutes or may be interpreted
     as a limitation upon, or waiver of, any privileges and immunities
     that apply to the Licensor or You, including from the legal
     processes of any jurisdiction or authority.


=======================================================================

Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.

Creative Commons may be contacted at creativecommons.org.

0707010000000C000041ED0000000000000000000000026258A65C00000000000000000000000000000000000000000000003C00000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/api0707010000000D000081A40000000000000000000000016258A65C00003765000000000000000000000000000000000000004400000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/api/LICENSECreative Commons

Creative Commons Legal Code
Attribution-ShareAlike 2.5

https://creativecommons.org/licenses/by-sa/2.5/legalcode

CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
License

THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.

BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.

1. Definitions

"Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the Work in its entirety in unmodified form, along with a number of other contributions, constituting separate and independent works in themselves, are assembled into a collective whole. A work that constitutes a Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this License.
"Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works, such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound recording, art reproduction, abridgment, condensation, or any other form in which the Work may be recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered a Derivative Work for the purpose of this License.
"Licensor" means the individual or entity that offers the Work under the terms of this License.
"Original Author" means the individual or entity who created the Work.
"Work" means the copyrightable work of authorship offered under the terms of this License.
"You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
"License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike.
2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws.

3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:

to reproduce the Work, to incorporate the Work into one or more Collective Works, and to reproduce the Work as incorporated in the Collective Works;
to create and reproduce Derivative Works;
to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission the Work including as incorporated in Collective Works;
to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission Derivative Works.
For the avoidance of doubt, where the work is a musical composition:

Performance Royalties Under Blanket Licenses. Licensor waives the exclusive right to collect, whether individually or via a performance rights society (e.g. ASCAP, BMI, SESAC), royalties for the public performance or public digital performance (e.g. webcast) of the Work.
Mechanical Rights and Statutory Royalties. Licensor waives the exclusive right to collect, whether individually or via a music rights society or designated agent (e.g. Harry Fox Agency), royalties for any phonorecord You create from the Work ("cover version") and distribute, subject to the compulsory license created by 17 USC Section 115 of the US Copyright Act (or the equivalent in other jurisdictions).
Webcasting Rights and Statutory Royalties. For the avoidance of doubt, where the Work is a sound recording, Licensor waives the exclusive right to collect, whether individually or via a performance-rights society (e.g. SoundExchange), royalties for the public digital performance (e.g. webcast) of the Work, subject to the compulsory license created by 17 USC Section 114 of the US Copyright Act (or the equivalent in other jurisdictions).
The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. All rights not expressly granted by Licensor are hereby reserved.

4. Restrictions.The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:

You may distribute, publicly display, publicly perform, or publicly digitally perform the Work only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy or phonorecord of the Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Work that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Work itself to be made subject to the terms of this License. If You create a Collective Work, upon notice from any Licensor You must, to the extent practicable, remove from the Collective Work any credit as required by clause 4(c), as requested. If You create a Derivative Work, upon notice from any Licensor You must, to the extent practicable, remove from the Derivative Work any credit as required by clause 4(c), as requested.
You may distribute, publicly display, publicly perform, or publicly digitally perform a Derivative Work only under the terms of this License, a later version of this License with the same License Elements as this License, or a Creative Commons iCommons license that contains the same License Elements as this License (e.g. Attribution-ShareAlike 2.5 Japan). You must include a copy of, or the Uniform Resource Identifier for, this License or other license specified in the previous sentence with every copy or phonorecord of each Derivative Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Derivative Works that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder, and You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Derivative Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Derivative Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Derivative Work itself to be made subject to the terms of this License.
If you distribute, publicly display, publicly perform, or publicly digitally perform the Work or any Derivative Works or Collective Works, You must keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or (ii) if the Original Author and/or Licensor designate another party or parties (e.g. a sponsor institute, publishing entity, journal) for attribution in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; the title of the Work if supplied; to the extent reasonably practicable, the Uniform Resource Identifier, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and in the case of a Derivative Work, a credit identifying the use of the Work in the Derivative Work (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). Such credit may be implemented in any reasonable manner; provided, however, that in the case of a Derivative Work or Collective Work, at a minimum such credit will appear where any other comparable authorship credit appears and in a manner at least as prominent as such other comparable authorship credit.
5. Representations, Warranties and Disclaimer

UNLESS OTHERWISE AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE MATERIALS, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.

6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

7. Termination

This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Derivative Works or Collective Works from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
8. Miscellaneous

Each time You distribute or publicly digitally perform the Work or a Collective Work, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
Each time You distribute or publicly digitally perform a Derivative Work, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.
Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor.

Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, neither party will use the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time.

Creative Commons may be contacted at https://creativecommons.org/.
0707010000000E000081A40000000000000000000000016258A65C000090F2000000000000000000000000000000000000004700000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/api/index.adoc////
This is "asciidoctor", not plain "asciidoc".
https://asciidoctor.org/docs/user-manual/

100 characters per line (soft limit).

////


////
This documentation is licensed under the Creative Commons Attribution-ShareAlike 2.5 International License.
To view a copy of this license, visit https://creativecommons.org/licenses/by-sa/2.5/legalcode or send a
letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
A copy of the license has been provided in the file documentation/API/LICENSE.
////


////
The revnumber API 1.1.1 below is autogenerated. Please do not touch this line.
////

:authors: Jonathan Moore Liles, Nils Hilbricht
:revnumber: API 1.1.2
:revremark: License CC-By-SA v2.5
:iconfont-remote!:
:!webfonts:

:sectnums:
:sectnumlevels: 4

:toc:
:toc-title: Table of Contents
:toclevels: 4


= New Session Manager - API

IMPORTANT: "New Session Manager" is a community version of the
link:http://non.tuxfamily.org/nsm/API.html["Non Session Manager" by Jonathan Moore Liles], who also
wrote the majority of this API document, especially the API itself. *The API is the same*. Any
technical changes or differences in behaviour are described in <<API Versions and Behaviour Changes>>.
All other changes to this document can be reviewed by accessing the git log. This document is
licensed under CC-By-Sa v2.5. See link:https://github.com/jackaudio/new-session-manager/tree/master/docs/src/api[LICENSE]


IMPORTANT: The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD
NOT", "RECOMMENDED",  "MAY", and "OPTIONAL" in this document are to be interpreted as
described in link:https://tools.ietf.org/html/rfc2119[RFC 2119].


The "New Session Manager"-API is used by many music and audio programs in Linux distributions
to allow any number of independent programs to be managed together as part of a logical session
(i.e. a song). Thus, operations such as loading and saving are synchronized.

The API comprises a simple Open Sound Control (OSC) based protocol, along with some behavioral
guidelines, which can easily be implemented by various applications.

This project contains a program called `nsmd` which is an implementation of the server side of
the NSM API. `nsmd` can be controlled by direct OSC messages, or (more commonly) a GUI:
Included in this package is the `nsm-legacy-gui`, which gets symlinked to "non-session-manager`.
Another GUI is "Agordejo". Other applications exist that (partially) support the NSM API and are able
to load clients, but they do not use the New-Session-Manager (or Non-Session-Manager) implementation
and are therefore out of scope for this document.

However, the same server-side API can also be implemented by other programs (such as Carla),
although consistency and robustness will likely suffer if non-NSM compliant clients are allowed to
participate in a session.

There is no direct dependency for client implementations, as long as they
can send and receive OSC.
Some clients use `liblo` (the OSC library), which becomes a dependency if you choose to implement
NSM-support with the provided header file `nsm.h` (`extras/nsm.h/nsm.h` in the git repository).
Some clients use the provided single-file python library `pynsm` (`extras/pynsm/nsmclient.py` in the git repository)
which has no dependencies outside the Python3 standard library.


The aim of this project is to thoroughly define the behavior required of clients. Often the
difficulty with other session-management approaches has been not in implementing code-support for
them, but in not defining rules and behaviour clearly enough.

As written above unambiguous rules are created by using RFC 2119 in this document. For the good of
the user, these rules are meant to be followed and are non-negotiable. If an application does not
conform to this specification it should be considered broken. Consistency across applications under
session management is very important for a good user experience.


== Client Behavior Under Session Management

Most graphical applications make available to the user a common set of file operations, typically
presented under a File or Project menu.

These are: New, Open, Save, Save As, Close and Quit or Exit.

The following sub-sections describe how these options should behave when the application is part of
an NSM session. These rules only apply when session management is active, that is, after the
`announce` handshake described in the <<NSM OSC Protocol>> section. In order to provide a
consistent and predictable user experience, it is critically important for applications to adhere
to these guidelines.


=== File Menu


==== New

This option MAY empty/reset the current file or project (possibly after user confirmation).
It MUST NOT allow the user to create a new project/file in another location.


==== Open

This option MUST be disabled.

The application MAY elect to implement an option called "Import into Session", which creates a
copy of a file/project which is then saved at the session path provided by NSM.


==== Save

This option should behave as normal, saving the current file/project as established by the NSM
`open` message.

This option MUST NOT present the user with a choice of where to save the file.


==== Save As

This option MUST be disabled.

The application MAY elect to implement an option called 'Export from Session', which
creates a copy of the current file/project which is then saved in a user-specified location outside
of the session path provided by NSM.


==== Close (as distinguished from Quit or Exit)

This option MUST be disabled unless its meaning is to disconnect the application from session
management.


====  Quit or Exit

This option MAY behave as normal (possibly asking the user to confirm exiting), or MAY do nothing
to only allow quit from the session-manager control.
When the client supports :optional-gui: this option SHOULD be replaced with hiding the client's GUI
so a quit by window manager hides.


===  Data Storage


==== Internal Files

All project specific data created by a client MUST be stored in the per-client storage area
provided by NSM. This includes all recorded audio and MIDI files, snapshots, etc. Only global
configuration items, exports, and renders of the project may be stored elsewhere (wherever the user
specifies).


==== External Files

Files required by the project but external to it (typically read-only data such as audio samples)
SHOULD be referenced by creating a symbolic link within the assigned session area, and then
referring to the symlink. This allows sessions to be archived and transported simply (e.g. with
"tar -h") by tools that have no knowledge of the project formats of the various clients in the
session. The symlinks thus created should, at the very least, be named after the files they refer
to. Some unique component may be required to prevent collisions.

==== Session Root and Session Directories

Client programs MUST NOT handle the following themselves. This section is background-information.

NSM follows the link:https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html[XDG Base Directory Specifications]

All existing and new sessions are directories below the session-root, which defaults to
`$XDG_DATA_HOME/nsm/`, which usually results in `$HOME/.local/share/nsm/`.

Each session directory contains a file `session.nsm` with one client per line `name:executable:UID\n`
For example:
```
JACKPatch:jackpatch:nBEIQ
jack_mixer:jack_mixer:nTXHV
Carla-Rack:carla-rack:nFAOD
```
`nsmd` loads and saves this file, client names are their self-reported names.
The file format is final and frozen. Additions or changes SHALL NOT be made.

===== Subdirectories / Hierarchical Structure

Subdirectories MAY be made to organize sessions into meaningful structures, such as album/track or
composer/genre/piece. For example: `Johann Sebastian Bach/Kantaten/Wie schön leuchtet der Morgenstern`.
Which results in the same directory structure on disk. Session names can contain any characters that
are supported by the underlying file system, usually UTF-8.

Subdirectories are created by either `nsmd` itself or by the users themselves, through their file
manager or a GUI (while the session is not open).

The project_name from `/nsm/server/new s:project_name` accepts the format `a/b/c/d`.

Any session itself MUST be a "leaf" in this directory tree. A session MUST NOT contain further
session subdirectories: any directory that contains a file `session.nsm` is the final
element in the hierarchy.

===== Write-Protection for Session Templates

Write protection for a whole session directory can either happen by "accident" (files from
another user, a network mount etc.) or on purpose, to protect a session template against accidental
changes. The latter is possible with a recursive `chown`, `chmod` or `chattr -R +i session-dir`.

nsmd itself just checks if `session.nsm` is read-only. In this case it will not send the save
command to it's session clients. This does not prevent hypothetical problems when the user
triggers a clients internal save command in a write protected directory. Clients SHOULD handle
their write protected save files themselves.

Advanced contraptions, like overlay filesystems or copy-on-write hardlinks to create read-only
sessions without the clients noticing, are out of scope for nsm.

===== Lockfiles

Because multiple `nsmd` can run at the same time we need to prevent accidental write-access to the
same session by different nsm-daemons, and subsequently GUIs.

Therefore each currently open session creates a lockfile under `$XDG_RUNTIME_DIR/nsm/` (usually
`/run/user/XXXX/nsm/`) that tells `nsmd` to not open such a locked session. This directory gets
cleaned by the operating system, preventing sessions to stay locked after e.g. a power failure.

The lockfile is named after the simple session name combined with a numeric ID for the session
root. It is possible that two `nsmd` opened two different session roots, both with the same simple
session name, e.g. "my song". Lockfiles are able to distinguish between those and will not prevent
access in this scenario. The numeric ID is a djb2 hash modulo (%) 65521 of the session root directory
(see `src/file.cpp` function `simple_hash()`).


The lockfile contains, on separate lines:

* The absolute path to the session, including the root-dir, which could be overriden by `nsmd --session-root`, allowing two sessions of the same basic name in different roots.
* the OSC URL of the server that runs this session, the same as `$NSM_URL`.
* the PID of `nsmd`

Example:
```
/home/johann/.local/share/nsm/cantatas/easter1751
osc.udp://myuser.localdomain:11287/
3022
```

===== Daemon Discovery

Each running `nsmd`, per user, creates a state file under `$XDG_RUNTIME_DIR/nsm/d/` (usually
`/run/user/XXXX/nsm/d/`) that can be used to look up running daemons, even if no session is loaded.
The name of the file is `nsmd` PID and the files contain their daemons osc.udp URL that is
compatible with the --nsm-url parameter of the GUI.


This enables you to e.g. start nsmd at boot with a random free port. Server-control programs such
as GUIs can then use this to look for running servers without requiring the user to look up and
input an osc URL manually as command line parameter.


== NSM OSC Protocol

All message parameters are REQUIRED. All messages MUST be sent from the same socket as the `announce`
message, using the `lo_send_from` method of liblo or its equivalent, as the server uses the return
addresses to distinguish between clients.


Clients MUST create thier OSC servers using the same protocol (UDP,TCP) as found in `NSM_URL`.
`nsmd` itself is using UDP only.



=== Establishing a Connection

==== Announce

At launch, the client MUST check the environment for the value of `NSM_URL`. If present, the client
MUST send the following message to the provided address as soon as it is ready to respond to the
`/nsm/client/open` event:

[source%nowrap,OSC]
----
/nsm/server/announce s:application_name s:capabilities s:executable_name i:api_version_major i:api_version_minor i:pid
----

If `NSM_URL` is undefined, invalid, or unreachable, then the client should proceed assuming that
session management is unavailable.

`api_version_major` and `api_version_minor` must be the two parts of the version number of the NSM API
as defined by this document.

Note that if the application intends to register JACK clients, `application_name` MUST be the same as
the name that would normally be passed to `jack_client_open`. For example, Non-Mixer sends
"Non-Mixer" as its `application_name`. Applications MUST NOT register their JACK clients until
receiving an `open` message; the `open` message will provide a unique client name prefix suitable for
passing to JACK. This is probably the most complex requirement of the NSM API, but it isn't
difficult to implement, especially if the application simply wishes to delay its initialization
process briefly while awaiting the `announce` reply and subsequent `open` message.

`capabilities` MUST be a string containing a colon separated list of the special capabilities the
client possesses. e.g. `:dirty:switch:progress:`

`executable_name` MUST be the executable name that the program was launched with. For C programs,
this is simply the value of `argv[0]`. Note that hardcoding the name of the program here is not the
same as using, as the user may have launched the program from a script with a different name using
exec, or have created a symlink to the program. Getting the correct value in scripting languages
like Python can be more challenging.

.Available Client Capabilities
[options="header", stripes=even]
|===

|Name | Description

|switch         | client is capable of responding to multiple `open` messages without restarting
|dirty          | client knows when it has unsaved changes
|progress       | client can send progress updates during time-consuming operations
|message        | client can send textual status updates
|optional-gui   | client has an optional GUI

|===


==== Response

The server will respond to the client's announce message with the following message:

[source%nowrap,OSC]
----
/reply "/nsm/server/announce" s:message s:name_of_session_manager s:capabilities
----

`message` is a welcome message.

The value of `name_of_session_manager` will depend on the implementation of the NSM server. It
might say "New Session Manager", or it might say "Non Session Manager" etc. This is for display to
the user.

`capabilities` will be a string containing a colon separated list of special server capabilities.

Presently, the server `capabilities` are:

.Available Server Capabilities
[options="header", stripes=even]
|===

|Name | Description

|server-control | client-to-server control
|broadcast      | server responds to /nsm/server/broadcast message
|optional-gui   | server responds to optional-gui messages. This capability is always present and MUST be supported by any server implementation.

|===

A client should not consider itself to be under session management until it receives this response.
For example, the Non applications activate their "SM" blinkers at this time.

If there is an error, a reply of the following form will be sent to the client:


[source%nowrap,OSC]
----
/error "/nsm/server/announce" i:error_code s:error_message
----

The following table defines possible values of `error_code`:

.Response codes
[options="header", stripes=even]
|===

|Code | Meaning

|ERR_GENERAL            | General Error
|ERR_INCOMPATIBLE_API   | Incompatible API version
|ERR_BLACKLISTED        | Client has been blacklisted.

|===


=== Server to Client Control Messages

Compliant clients MUST accept the client control messages described in this section. All client
control messages REQUIRE a response. Responses MUST be delivered back to the sender (`nsmd`) from the
same socket used by the client in its `announce` message (by using `lo_send_from`) AFTER the action has
been completed or if an error is encountered. The required response is described in the subsection
for each message.

If there is an error and the action cannot be completed, then `error_code` MUST be set to a valid
error code (see <<Error Code Definitions>>) and `message` to a string describing the problem
(suitable for display to the user).

The reply can take one of the following two forms, where path MUST be the `path` of the message being
replied to (e.g. "nsm/client/save":

[source%nowrap,OSC]
----
/reply s:path s:message
----

[source%nowrap,OSC]
----
/error s:path i:error_code s:message
----


==== Quit

There is no message for this. Clients will receive the Unix SIGTERM signal and MUST close cleanly
IMMEDIATELY, without displaying any kind of dialog to the user and regardless of whether or not
unsaved changes would be lost. When a session is closed the application will receive this signal
soon after having responded to a `save` message.


[#server-to-client-control-messages-open]
==== Open

[source%nowrap,OSC]
----
/nsm/client/open s:path_to_instance_specific_project s:display_name s:client_id
----

`path_to_instance_specific_project` is a path name in the form client_name.ID, assigned to the
client for storing its project data. The client MUST choose one of the four strategies below to
save, so that every file in the session can be traced back to a client and, vice versa, a client
name.ID can be used to look up all its files. (For example to clean up the session dir)

* The client has no state and does not save at all
  ** and it MUST NOT misuse e.g. ~/.config to save session specific information e.g. synth-instrument settings
* The client may use the path client_name.ID directly, resulting in a file client_name.ID in the session directory
* The client may append its native file extension (e.g. `.json`) to the path client_name.ID
* The client may use the path as directory, creating arbitrary files below, for example recorded .wav.
 ** and it MUST NOT use the client ID below this point. This way the data stays transferable by hand to another client instance (in another session).
 ** best case practice is to always use the same file names, for example `client_name.ID/savefile.json`

If a project exists at the path, the client MUST immediately open it.

If a project does not exist at the path, then the client MUST immediately create and open a new one
at the specified path or, for clients which hold all their state in memory, store the path for
later use when responding to the `save` message.

No file or directory will be created at the specified path by the server. It is up to the client to
create what it needs.

For clients which HAVE NOT specified the `:switch:` capability, the `open` message will only be
delivered once, immediately following the `announce` response.

For clients which HAVE specified the `:switch:` capability, the client MUST immediately switch to the
specified project or create a new one if it doesn't exist.

Clients which are incapable of switching projects or are prone to crashing upon switching MUST NOT
include `:switch:` in their capability string.

If the user the is allowed to run two or more instances of the application simultaneously
then such an application MUST PRE-PEND the provided `client_id` string, followed by "/", to any
names it registers with common subsystems (e.g. JACK client names). This ensures that multiple
instances of the same application can be restored in any order without scrambling the JACK
connections or causing other conflicts.

The provided `client_id` will be a concatenation of the value of `application_name` sent by the
client in its `announce` message and a unique identifier.

Therefore, applications which create single JACK clients can use the value of `client_id` directly
as their JACK client name.

Applications which register multiple JACK clients (e.g. Carla or Non-Mixer) MUST PRE-PEND
`client_id` value, followed by "/", to the client names they register with JACK and the application
determined part MUST be unique for that (JACK) client.

For example, Carla is a plugin-host that loads each plugin as JACK client.
Suitable JACK client names are: `carla-jack-multi.nBAF/ZynAddSubFx` or `carla-jack-multi.nBAF/Helm`
Please note that ZynAddSubFx and Helm are *not ports* but clients. Each of them can have any number
of audio and midi ports below them.

Note that this means that the application MUST NOT register with JACK (or any
other subsystem requiring unique names) until it receives an `open` message from NSM. Likewise,
applications with the `:switch:` capability should close their JACK clients and re-create them with
using the new `client_id` (renaming JACK-clients is not possible, only ports).

A response is REQUIRED as soon as the open operation has been completed. Ongoing progress MAY be
indicated by sending messages to `/nsm/client/progress`.


===== Response

The client MUST respond to the 'open' message with:

[source%nowrap,OSC]
----
/reply "/nsm/client/open" s:message
----

Or

[source%nowrap,OSC]
----
/error "/nsm/client/open" i:error_code s:message
----


.Response codes
[options="header", stripes=even]
|===

|Code | Meaning

|ERR                  | General Error
|ERR_BAD_PROJECT      | An existing project file was found to be corrupt
|ERR_CREATE_FAILED    | A new project could not be created
|ERR_UNSAVED_CHANGES  | Unsaved changes would be lost
|ERR_NOT_NOW          | Operation cannot be completed at this time

|===


==== Save

[source%nowrap,OSC]
----
/nsm/client/save
----

This message will only be delivered after a previous `open` message, and may be sent any number of
times within the course of a session (including zero, if the user aborts the session).

===== Response

[source%nowrap,OSC]
----
/reply "/nsm/client/save" s:message
----

Or

[source%nowrap,OSC]
----
/error "/nsm/client/save" i:error_code s:message
----


.Response codes
[options="header", stripes=even]
|===

|Code | Meaning

|ERR                 | General Error
|ERR_SAVE_FAILED     | Project could not be saved
|ERR_NOT_NOW         | Operation cannot be completed at this time

|===


=== Server to Client Informational Messages

==== Session is Loaded

Accepting this message is optional. The intent is to signal to clients which may have some
interdependence (say, peer to peer OSC connections) that the session is fully loaded and all their
peers are available. Most clients will not need to act on this message. This message has no meaning
when a session is being built or run; only when it is initially loaded. Clients who intend to act
on this message MUST NOT do so by delaying initialization waiting for it.

[source%nowrap,OSC]
----
/nsm/client/session_is_loaded
----

This message does not require a response.


==== Show Optional Gui

If the client has specified the `optional-gui` capability, then it may receive this message from the
server when the user wishes to change the visibility state of the GUI. It doesn't matter if the
optional GUI is integrated with the program or if it is a separate program \(as is the case with
SooperLooper\). When the GUI is hidden, there should be no window mapped and if the GUI is a
separate program, it should be killed.

[source%nowrap,OSC]
----
/nsm/client/show_optional_gui
----

[source%nowrap,OSC]
----
/nsm/client/hide_optional_gui
----

This message does not require a response.



=== Client to Server Informational Messages

==== Optional GUI

If the client has specified the `optional-gui` capability, then it MUST send this message whenever
the state of visibility of the optional GUI has changed. It also MUST send this message after its
announce message to indicate the initial visibility state of the optional GUI.

The client SHOULD always start hidden, if not saved as visible. That implies the first load, after
adding to the session, SHOULD always be hidden.

It is the responsibility of the client to remember the visibility state of its GUI across session
loads.

[source%nowrap,OSC]
----
/nsm/client/gui_is_hidden
----

[source%nowrap,OSC]
----
/nsm/client/gui_is_shown
----

No response will be delivered.


==== Progress

[source%nowrap,OSC]
----
/nsm/client/progress f:progress
----

For potentially time-consuming operations, such as `save` and `open`, progress updates may be
indicated throughout the duration by sending a floating point value between 0.0 and 1.0, 1.0
indicating completion, to the NSM server.

The server will not send a response to these messages, but will relay the information to the user.

Note that even when using the `progress` feature, the final response to the `save` or `open`
message is still REQUIRED.

Clients which intend to send progress messages MUST include `:progress:` in their `announce`
capability string.


==== Dirtiness

[source%nowrap,OSC]
----
/nsm/client/is_dirty
----

[source%nowrap,OSC]
----
/nsm/client/is_clean
----

Some clients may be able to inform the server when they have unsaved changes pending. Such clients
may optionally send `is_dirty` and `is_clean` messages.

Clients which have and use this capability MUST include `:dirty:` in their `announce` capability string.

==== Status Messsages

[source%nowrap,OSC]
----
/nsm/client/message i:priority s:message
----

Clients may send miscellaneous status updates to the server for possible display to the user. This
may simply be chatter that is normally written to the console. `priority` MUST be a number from 0
to 3, 3 being the most important.

Clients which have and use this capability MUST include `:message:` in their `announce` capability
string.


=== Error Code Definitions

.Error Code Definitions
[options="header", stripes=even]
|===

|Symbolic Name   | Integer Value

|ERR_GENERAL            | -1
|ERR_INCOMPATIBLE_API   | -2
|ERR_BLACKLISTED        | -3
|ERR_LAUNCH_FAILED      | -4
|ERR_NO_SUCH_FILE       | -5
|ERR_NO_SESSION_OPEN    | -6
|ERR_UNSAVED_CHANGES    | -7
|ERR_NOT_NOW            | -8
|ERR_BAD_PROJECT        | -9
|ERR_CREATE_FAILED      | -10

|===

=== Client to Server Control

If the server publishes the `:server-control:` capability, then clients can also initiate action by
the server. For example, a client might implement a 'Save All' option which sends a
`/nsm/server/save` message to the server, rather than requiring the user to switch to the session
management interface to effect the save.


=== Server Control API

The session manager not only manages clients via OSC, but it is itself controlled via OSC messages.
The server responds to the following messages.

All of the following messages will be responded to, at the sender's address, with one of the two
following messages:

[source%nowrap,OSC]
----
/reply s:path s:message
----

[source%nowrap,OSC]
----
/error s:path i:error_code s:message
----

The first parameter of the reply is the path to the message being replied to. The `/error` reply
includes an integer error code (non-zero indicates error). `message` will be a description of the
error.

The possible errors are:

.Responses
[options="header", stripes=even]
|===

|Code   |Meaning

|ERR_GENERAL         | General Error
|ERR_LAUNCH_FAILED   | Launch failed
|ERR_NO_SUCH_FILE    | No such file
|ERR_NO_SESSION      | No session is open
|ERR_UNSAVED_CHANGES | Unsaved changes would be lost

|===


* `/nsm/server/add s:executable_name`
  ** Adds a client to the current session.

* `/nsm/server/save`
  ** Saves the current session.

* `/nsm/server/open s:project_name`
  ** Saves the current session and loads a new session.

* `/nsm/server/new s:project_name`
  ** Saves the current session and creates a new session.

* `/nsm/server/duplicate s:new_project`
  ** Saves and closes the current session, makes a copy, and opens it.

* `/nsm/server/close`
  ** Saves and closes the current session.

* `/nsm/server/abort`
  ** Closes the current session WITHOUT SAVING

* `/nsm/server/quit`
  ** Saves and closes the current session and terminates the server.

* `/nsm/server/list`
  ** Lists available projects. One `/reply` message will be sent for each existing project.
  ** Afer listing the last session one final `/reply` with `/nsm/server/list, ""` will be send. That is an empty string.


==== Client to Client Communication

If the server includes `:broadcast:` in its capability string, then clients may send broadcast
messages to each other through the NSM server. Clients may send messages to the server at the path
`/nsm/server/broadcast`.

The format of this message is as follows:

[source%nowrap,OSC]
----
/nsm/server/broadcast s:path [arguments...]
----

The message will then be relayed to all clients in the session at the path `path` (with the
arguments shifted by one).

For example the message:


[source%nowrap,OSC]
----
/nsm/server/broadcast /tempomap/update "0,120,4/4:12351234,240,4/4"
----

Would broadcast the following message to all clients in the session (except for the sender), some
of which might respond to the message by updating their own tempo maps.


[source%nowrap,OSC]
----
/tempomap/update "0,120,4/4:12351234,240,4/4"
----

The Non programs use this feature to establish peer to peer OSC communication by symbolic names
(client IDs) without having to remember the OSC URLs of peers across sessions.


== API Versions and Behaviour Changes

Here we will document all technical changes or differences in behaviour together with their API and
project version numbers. The term "original" refers to Non Session Manager and "new" refers to New
Session Manager.

Version numbers follow link:https://semver.org/spec/v2.0.0.html[Semantic Versioning 2.0.0]

.Semantic Versioning Scheme
```
Given a version number MAJOR.MINOR.PATCH, increment the:

MAJOR version when you make incompatible API changes,
MINOR version when you add functionality in a backwards compatible manner, and
PATCH version when you make backwards compatible bug fixes.
```


.NSM Version Numbers
[options="header", stripes=even]
|===

|Subject     | Version

|Non Session Manager at moment of fork          | 1.2 (June 2020)
|Non Session Manager API    | 1.0 link:https://github.com/original-male/non/blob/master/session-manager/src/nsmd.C[NON nsmd.C]
|Original API Document      | 1.0 link:http://non.tuxfamily.org/nsm/API.html[non.tuxfamily.org/nsm/API.html]
|New Session Manager        | 1.6.0
|New Session Manager API    | 1.1.2 link:https://github.com/jackaudio/new-session-manager/blob/master/src/nsmd.cpp[NEW nsmd.cpp]
|New API Document   | 1.5.0 link:#[Here]

|===


=== Guidelines

The most important factor in decision making is to keep client compatibility at 100%.
No client will ever receive an unrequested OSC message except those in API 1.0.0.

Messages that drastically change existing `/nsm/client/` or `/nsm/server` behaviour require an
inrecement to `API_VERSION_MAJOR`, which we want to avoid.

`nsmd` checks if the clients `API_VERSION_MAJOR` is greater than its own and refuses the client
with `ERR_INCOMPATIBLE_API`.

All changes (that concern client/server behaviour) that increment `API_VERSION_MINOR` will be
request-only or gated by new capabilities (e.g. `:optional-gui:`). `nsmd` will not send any
messages if a capability was not sent by the client in <<Announce,`announce`>>. This includes
mostly optional features about requesting extra information.

New actions for server-control, for example a hypothetical `/nsm/server/save_as`, which would be
triggered by the client and would only be *answered* by the server ("no unrequested message") will
increment `API_VERSION_MINOR`.

All changes that increment `API_VERSION_PATCH` will not have any effect on behaviour, except to
fix clear problems, where "problem" is defined by having a different effect than described in this
document, which includes technical problems such as crashes.

All messages regarding GUI-communication that start with `/nsm/gui/...` were undocumented in API
1.0.0 and only used by `non-session-manager` / `nsm-legacy-gui`. Until properly documented in this
document this part of the API is considered unstable and may change at any time without notice.
However, when changing already existing messages and behaviour it MAY increment `API_VERSION_MINOR`
or `API_VERSION_PATCH`. In that case it will appear in the list below.

Last factor of compatibility is that any unknown message sent to `nsmd` will just print a warning
message to stdout, but will otherwise be ignored. This secures a stable server, even when a client
misbehaves and sends too-new messages outside of announced :capabilites:

=== Changes in API Version 1.1.0

Rewritten API document without code changes to adapt to existing code or existing client behaviour:

* Changed versioning scheme to Semantic Versioning with three positions Major.Minor.Patch
* <<Quit or Exit>> SHOULD hide instead of exiting when :optional-gui: is supported and MAY not
    act on the quit through menu otherwise.
* <<#server-to-client-control-messages-open,Open>>: Make clear that there are only certain
    possibilities for save paths. We added MUST because the rule was just implied before.
* <<#server-to-client-control-messages-open,Open>>: Make clear that the delimiter for
    multi-jack clients is "/".
* <<Optional GUI>> SHOULD start hidden, always after a fresh add to the session. After that saving
    the visibility state may override it for next time.
* <<Progress>> MUST be announced in :capabilities: . Before there was a lower case "should",
    which means nothing. Parallel-examples in the specs cleary say that supporting optional features must be announced first.
  ** Same for <<Dirtiness>> and <<Status Messsages>>.
* <<Status Messsages>> have priority numbers between 0 and 3, so they MUST send that.
    It was never an arbitrary value.

Code changes:

* <<Server Control API>>: `/nsm/server/list` chain of single OSC messages, one for each session,
    is now finalized with sending and empty string "" as session name. Previously this was just
    a symbolically irrelevant console message `"Done."`
* Replies to `/nsm/server/save` etc. will now be sent back to the sender and not falsely to the last
   client who replied to `/nsm/client/save`.  This alone would only require API_VERSION_PATCH
   increment, but we are already incrementing minor.
* <<Server Control API>>: `/nsm/server/add` was replying with an undocumented error code on success.
   Instead, as this document always specificed, it now sends `"/reply", path, "Launched."`.
   Again, this would have been just API_VERSION_PATCH on its own.

Undocumented (Unstable) `/nsm/gui` protocol

* Send client status after a GUI attaches to running server. This
    was not happening before, but it was the intention. It was just broken in nsmd.cpp. This alone
    would only require API_VERSION_PATCH increment, but we are already incrementing minor.
* Send label "launch error!" when a program is added (or loaded) that
    does not exist in $PATH. This requires no adaptation of any client, server or GUI because labels
    are arbitrary already and this is not meant for automatic parsing, but as user information.
* `/nsm/gui/session/name` will now always send the same parameter format, regardless of how the session was opened:
   simple-session-name, relative session path with subdirs below session-root.
* When a GUI announces itself to nsmd it will receive the absolute path to the session directory
    through the message `/nsm/gui/session/root`. This is not a new addition but was already in
    non-session-manager git.

=== Changes in API Version 1.1.1

* Server-capability :optional-gui: is now mandatory for SERVER implementations. Reasoning:
This is an  important core feature of NSM and thus will be treated as such by guaranteeing it to exist.
After looking at all currently known clients and server-implementations it turns out that all servers
support :optional-gui: and the vast majority of clients not only support it, but actually assume it
and do _not_ test for the server capability, as it was written in this document.
There are now two choices: Adjust this document to the (good) reality or consider all clients broken.
Summary: We consider this API document wrong and therefore fix it, thus increasing API version
patch-level from 1.1.0 to 1.1.1

* Add API-section "Subdirectories / Hierarchical Structure" that explains the session directory.
This behaviour was already the case for nsm-legacy-gui and nsmd 1.5.0 was patched to adhere to this
behaviour more strictly as well, removing false session entries in 3rd party clients such as Agordejo.

=== Changes in API Version 1.1.2

* nsmd now follows the XDG Base Directory Specifications for it's session root and lock files. This
if of no consequence to clients but required documentation nevertheless, which was described as
"background information" in the chapters for lock files and daemon disovery.

* nsmd now gracefully handles read-only `session.nsm` files. This theoretically enables read-only
sessions and session-templates.  It is included in the patch-level because this was marked as a
long-standing `FIXME` in the code by the original author. Or in other words: just a bug fix.
0707010000000F000081A40000000000000000000000016258A65C000003EA000000000000000000000000000000000000004700000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/api/readme.txtThe statements made here only concern the API document. All other documentation and help texts of
the "New Session Manager" are original works.

This subdirectory "API" contains an adapted copy of http://non.tuxfamily.org/nsm/API.html, which
was released by Jonathan Moore Liles <male@tuxfamily.org> under the title "Non Session Management
API Version 1."

It was published on the the NON-Website on 2013-04-06 with git commit
d7cf8955b8557ccc56d108425a2c61b0e1ac73f4 under the Creative Commons By-Sa License 2.5, as was the
whole website (see website footer). For a full copy of the license please see the attached file
LICENSE.

This adaption and all changes are licensed under CC-By-Sa 2.5 as well. The original work was copied
from the NON-website by hand in 2020-07 by Nils Hilbricht.

Initial git commit on 2020-07-06 is the unaltered content by Jonathan Moore Liles, not counting
layout changes because the document format changed. All subsequent changes and authors can be
reviewed by git log.
07070100000010000081ED0000000000000000000000016258A65C00001095000000000000000000000000000000000000004400000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/generate.sh#!/bin/sh

#The documentation is built statically and does not belong to the normal build process.
#Updating is part of the development process, not compiling or packaging.
#Run this before a release, or any time you want to update the docs or the README.

#This script takes common snippets of information and updates or generates source info files from
#them.
# parse src/nsmd.cpp for API version, insert into /docs/src/api/index.adoc
# parse src/nsmd.cpp for package version, insert into /meson.build and /docs/src/index.adoc
# generate /README.md  (shares text with manual index)
# generate manpages
# convert all .adoc files to html in /docs/  (This enables github to directly present this dir as website)
#
# WARNING: You still need to manually edit the date and version in /CHANGELOG
#
#We do _not_ change the copyright date in files license-headers.
#They only exist to mark to year of the fork. In the future dates might be removed completely.

set -e  #Stop script on errors
set -u  #Trace unset variables as an error.

#Change pwd to root dir
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
cd "$parent_path"/../..
[ -f "CHANGELOG" ] || ( echo "not in the root dir"; exit 1 ) #assert correct dir
[ -f "build/nsmd" ] || ( echo "no build/ dir with binaries"; exit 1 ) #assert build was made, for manpages

#Gather data
ROOT=$(pwd) #save for later
VERSION=$(grep "define VERSION_STRING" "src/nsmd.cpp" | cut -d ' ' -f 3) #Get version as "1.4" string
VERSION="${VERSION%\"}"  #Remove "
VERSION="${VERSION#\"}"  #Remove "

_MAJORAPI=$(grep "define NSM_API_VERSION_MAJOR" "src/nsmd.cpp" | cut -d ' ' -f 3)
_MINORAPI=$(grep "define NSM_API_VERSION_MINOR" "src/nsmd.cpp" | cut -d ' ' -f 3)
_PATCHAPI=$(grep "define NSM_API_VERSION_PATCH" "src/nsmd.cpp" | cut -d ' ' -f 3)
APIVERSION=$_MAJORAPI"."$_MINORAPI"."$_PATCHAPI

#Present data to confirm write-action
echo "Root: $ROOT"
echo "Version: $VERSION"
echo "API Version: $APIVERSION"
echo "Please make sure that your meson build dir is up to date for manpage generation"
read -p "Is parsed data correct? Continue by writing files? [y|n] " -n 1 -r
if [[ ! $REPLY =~ ^[Yy]$ ]]
then
    echo
    echo "Abort"
    exit 1
fi
echo
echo

echo "Update meson.build version number"
cd "$ROOT"
sed -i "/^version :.*/c\version : '$VERSION'," meson.build #Find the version line and replace with entire new line

echo "Update docs to programs version number"
cd "$ROOT/docs/src"
sed -i '/^\:revnumber.*/c\:revnumber: '$VERSION index.adoc #Find the revnumber line and replace with entire new line

echo "Update API document to API version number"
cd "$ROOT/docs/src/api"
sed -i '/^\:revnumber.*/c\:revnumber: API '$APIVERSION index.adoc #Find the revnumber line and replace with entire new line


echo "Generate README from snippets"
cd "$ROOT/docs/src"
cat "readme-00.md" "readme-01.md" "readme-02.md" > "$ROOT/README.md"


echo "Generate website and documentation with Asciidoctor using README snippets"
echo "  We generate directly into docs/ and not into e.g. docs/out because github can read docs/ directly."
cd "$ROOT/docs/"
mkdir -p "api"
asciidoctor src/index.adoc -o index.html
asciidoctor src/api/index.adoc -o api/index.html


echo "Generate all manpages"
cd "$ROOT/docs/src" #We tested earlier that a build-dir exists

help2man ../../build/nsmd --version-string="nsmd Version $VERSION" --no-info --include manpage-common.h2m > nsmd.1
help2man ../../build/nsm-legacy-gui --version-string="nsm-legacy-gui Version $VERSION" --no-info --include manpage-common.h2m > nsm-legacy-gui.1
help2man ../../build/nsm-legacy-gui --version-string="nsm-legacy-gui Version $VERSION" --no-info --include manpage-common.h2m > non-session-manager.1
help2man ../../build/nsm-proxy  --version-string="nsm-proxy Version $VERSION" --no-info --include manpage-common.h2m > nsm-proxy.1
help2man ../../build/nsm-proxy-gui  --version-string="nsm-proxy-gui Version $VERSION" --no-info --include manpage-common.h2m > nsm-proxy-gui.1
help2man ../../build/jackpatch  --version-string="jackpatch Version $VERSION" --no-info --include manpage-common.h2m > jackpatch.1

echo
echo "Don't forget to adjust the version and date in CHANGELOG manually."
echo "Finished. You need to commit your changes to git manually."
07070100000011000081A40000000000000000000000016258A65C00003AFC000000000000000000000000000000000000004200000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/icons.svg<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="210mm"
   height="297mm"
   viewBox="0 0 210 297"
   version="1.1"
   id="svg8"
   sodipodi:docname="icons.svg"
   inkscape:version="1.0 (4035a4fb49, 2020-05-01)">
  <defs
     id="defs2" />
  <sodipodi:namedview
     units="px"
     id="base"
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0.0"
     inkscape:pageshadow="2"
     inkscape:zoom="0.35"
     inkscape:cx="-962.06585"
     inkscape:cy="-215.33923"
     inkscape:document-units="mm"
     inkscape:current-layer="layer1"
     inkscape:document-rotation="0"
     showgrid="false"
     inkscape:window-width="2558"
     inkscape:window-height="1398"
     inkscape:window-x="0"
     inkscape:window-y="20"
     inkscape:window-maximized="1" />
  <metadata
     id="metadata5">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1">
    <g
       id="nsm"
       inkscape:label="nsm"
       transform="translate(-172.56864)">
      <title
         id="title888">nsm</title>
      <rect
         y="114.63333"
         x="71.133331"
         height="67.73333"
         width="67.73333"
         id="rect10"
         style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
      <text
         xml:space="preserve"
         style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
         x="105.32493"
         y="169.25755"
         id="text852"><tspan
           sodipodi:role="line"
           x="105.32493"
           y="169.25755"
           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
           id="tspan850">NSM</tspan></text>
    </g>
    <g
       transform="translate(-170.13191,80.919145)"
       inkscape:label="jackpatch"
       id="jackpatch">
      <title
         id="title890">jackpatch</title>
      <rect
         style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
         id="rect892"
         width="67.73333"
         height="67.73333"
         x="71.133331"
         y="114.63333" />
      <text
         id="text896"
         y="165.8237"
         x="106.38719"
         style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
         xml:space="preserve"><tspan
           id="tspan894"
           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
           y="165.8237"
           x="106.38719"
           sodipodi:role="line">JP</tspan></text>
    </g>
    <g
       id="proxy"
       inkscape:label="proxy">
      <title
         id="title940">proxy</title>
      <rect
         y="37.223072"
         x="-99.065941"
         height="67.73333"
         width="67.73333"
         id="rect902"
         style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
      <text
         xml:space="preserve"
         style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
         x="-65.639175"
         y="93.759422"
         id="text906"><tspan
           sodipodi:role="line"
           x="-65.639175"
           y="93.759422"
           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
           id="tspan904">PRX</tspan></text>
    </g>
    <path
       d="m 231.64549,37.223072 h 67.73333 V 104.9564 h -67.73333 z"
       style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
       id="rect1679" />
    <g
       style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
       id="text1683"
       aria-label="PRX">
      <path
         id="path1690"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 238.98532,48.420053 0.0677,45.339369 h 4.87229 V 72.578493 l 0.27069,-0.06767 h 4.60161 c 1.42108,0 2.63915,-0.406024 3.65421,-1.150401 1.01506,-0.744378 1.48876,-1.962451 1.48876,-3.654218 V 53.427685 c 0,-1.96245 -0.4737,-3.315864 -1.42109,-3.992571 -0.94739,-0.676707 -2.16546,-1.015061 -3.72188,-1.015061 z m 4.93996,4.872291 h 3.31587 c 0.47369,0 0.81205,0.135341 1.1504,0.473695 0.33835,0.338353 0.4737,0.744377 0.4737,1.218072 v 10.962654 c 0,0.473694 -0.13535,0.812048 -0.4737,1.150401 -0.33835,0.338354 -0.67671,0.473695 -1.1504,0.473695 h -3.31587 z" />
      <path
         id="path1692"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 262.66997,53.292344 h 3.31586 c 0.4737,0 0.81205,0.135341 1.15041,0.473695 0.33835,0.338353 0.47369,0.744377 0.47369,1.218072 v 10.962654 c 0,0.473694 -0.13534,0.812048 -0.47369,1.150401 -0.33836,0.338354 -0.67671,0.473695 -1.15041,0.473695 h -3.31586 z m -4.93996,-4.872291 0.0677,45.339369 h 4.87229 v -21.2486 h 1.21807 l 4.19559,21.2486 h 4.87229 L 268.69266,72.24014 c 0.60904,-0.203012 1.42109,-0.609037 2.50382,-1.218073 0.94739,-0.609036 1.48875,-1.759438 1.48875,-3.451206 V 53.427685 c 0,-1.285743 -0.47369,-2.436145 -1.48875,-3.518876 -1.01506,-1.015061 -2.16547,-1.488756 -3.51888,-1.488756 z" />
      <path
         id="path1694"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 275.6627,48.420053 5.0753,23.346392 -4.80462,21.789965 h 5.00763 l 2.90984,-13.53414 2.90984,13.53414 h 4.93996 l -4.73695,-21.789965 5.07531,-23.346392 h -5.14298 l -3.04518,14.752213 -3.04518,-14.752213 z" />
    </g>
    <path
       d="m 230.75774,195.55247 h 67.73333 v 67.73333 h -67.73333 z"
       style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
       id="rect1700" />
    <path
       d="m 256.70029,252.35951 v -50.95604 h -5.0753 v 49.46729 c 0,0.47369 -0.13534,0.81204 -0.4737,1.1504 -0.33835,0.33835 -0.6767,0.47369 -1.1504,0.47369 h -1.75944 v 4.93996 h 3.04518 c 3.58655,0 5.41366,-1.69176 5.41366,-5.0753 z"
       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;line-height:1.25;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
       id="path1712" />
    <path
       d="m 266.05212,201.40347 0.0677,45.33937 h 4.87229 v -21.18093 l 0.27068,-0.0677 h 4.60161 c 1.42108,0 2.63916,-0.40602 3.65422,-1.1504 1.01506,-0.74438 1.48875,-1.96245 1.48875,-3.65422 v -14.27851 c 0,-1.96245 -0.47369,-3.31587 -1.42108,-3.99257 -0.94739,-0.67671 -2.16546,-1.01507 -3.72189,-1.01507 z m 4.93996,4.87229 h 3.31586 c 0.4737,0 0.81205,0.13535 1.1504,0.4737 0.33836,0.33835 0.4737,0.74438 0.4737,1.21807 v 10.96266 c 0,0.47369 -0.13534,0.81204 -0.4737,1.1504 -0.33835,0.33835 -0.6767,0.47369 -1.1504,0.47369 h -3.31586 z"
       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;line-height:1.25;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
       id="path1714" />
    <path
       d="m 232.98119,114.63333 h 67.73333 v 67.73333 h -67.73333 z"
       style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
       id="rect1875" />
    <g
       style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
       id="text1879"
       aria-label="NSM">
      <path
         id="path1886"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 236.41654,123.6475 0.13535,0.27068 v 45.33937 h 5.00763 v -29.70743 l 8.79719,29.70743 h 5.34598 l -0.13534,-0.40602 V 123.6475 h -5.21064 v 29.63977 l -8.72952,-29.63977 z" />
      <path
         id="path1888"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 269.64283,169.52824 c 1.35341,0 2.50382,-0.4737 3.45121,-1.35342 1.01506,-0.94739 1.48875,-2.09779 1.48875,-3.4512 v -14.48153 c 0,-1.28575 -0.47369,-2.43615 -1.35341,-3.45121 -0.94739,-0.94739 -2.16546,-1.6241 -3.58655,-2.09779 l -3.65422,-1.1504 c -0.40602,-0.13534 -0.81205,-0.4737 -1.1504,-0.94739 -0.33835,-0.54137 -0.47369,-1.01506 -0.47369,-1.48876 v -10.82731 c 0,-0.4737 0.13534,-0.81205 0.47369,-1.1504 0.33835,-0.33836 0.67671,-0.4737 1.1504,-0.4737 h 1.6241 c 0.47369,0 0.81205,0.13534 1.1504,0.4737 0.33836,0.33835 0.4737,0.6767 0.4737,1.1504 v 3.51888 h 5.00763 v -5.27832 c 0,-1.42108 -0.4737,-2.57148 -1.42109,-3.51887 -1.01506,-0.94739 -2.16546,-1.42109 -3.51887,-1.42109 h -4.93996 c -1.42109,0 -2.57149,0.4737 -3.51888,1.42109 -0.94739,0.94739 -1.42108,2.09779 -1.42108,3.51887 v 14.21085 c 0,1.28574 0.47369,2.50381 1.35341,3.51888 0.94739,1.01506 2.16546,1.69176 3.58655,2.16546 l 3.65421,1.08273 c 0.4737,0.13534 0.81205,0.40602 1.15041,0.87972 0.33835,0.47369 0.47369,0.87972 0.47369,1.35341 v 11.16567 c 0,0.47369 -0.13534,0.87972 -0.47369,1.21807 -0.33836,0.33835 -0.67671,0.47369 -1.15041,0.47369 h -1.62409 c -0.4737,0 -0.81205,-0.13534 -1.1504,-0.47369 -0.33836,-0.33835 -0.4737,-0.74438 -0.4737,-1.21807 v -3.51888 h -5.00763 v 5.34599 c 0,1.35341 0.47369,2.50381 1.42108,3.4512 1.01506,0.87972 2.16547,1.35342 3.51888,1.35342 z" />
      <path
         id="path1890"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 278.30458,123.91818 0.13534,45.33937 h 5.14298 v -31.94057 l 4.19558,15.97029 h 0.67671 l 3.99257,-15.97029 v 31.94057 h 5.27831 v -45.33937 h -4.80462 l -4.80462,15.0229 -4.87229,-15.0229 z" />
    </g>
    <text
       id="text1896"
       y="14.555378"
       x="-90.511452"
       style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
       xml:space="preserve"><tspan
         style="stroke-width:0.264583"
         y="14.555378"
         x="-90.511452"
         id="tspan1894"
         sodipodi:role="line">Texts and</tspan><tspan
         id="tspan1898"
         style="stroke-width:0.264583"
         y="27.784504"
         x="-90.511452"
         sodipodi:role="line">Objects</tspan></text>
    <text
       id="text1902"
       y="8.2739525"
       x="239.56985"
       style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
       xml:space="preserve"><tspan
         style="stroke-width:0.264583"
         y="8.2739525"
         x="239.56985"
         id="tspan1900"
         sodipodi:role="line">Converted</tspan><tspan
         id="tspan1904"
         style="stroke-width:0.264583"
         y="21.503078"
         x="239.56985"
         sodipodi:role="line">to paths</tspan></text>
    <text
       id="text1908"
       y="31.889189"
       x="27.74991"
       style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
       xml:space="preserve"><tspan
         style="stroke-width:0.264583"
         y="31.889189"
         x="27.74991"
         id="tspan1906"
         sodipodi:role="line">Inkscape export single icon:</tspan><tspan
         id="tspan1994"
         style="stroke-width:0.264583"
         y="45.118317"
         x="27.74991"
         sodipodi:role="line">Select Icon, Ctrl+Shift+R</tspan><tspan
         id="tspan1910"
         style="stroke-width:0.264583"
         y="58.347443"
         x="27.74991"
         sodipodi:role="line">to resize to selection.</tspan><tspan
         id="tspan1912"
         style="stroke-width:0.264583"
         y="71.576561"
         x="27.74991"
         sodipodi:role="line">File -&gt; Save a Copy </tspan><tspan
         id="tspan1914"
         style="stroke-width:0.264583"
         y="84.805687"
         x="27.74991"
         sodipodi:role="line">Inkscape SVG works as icon</tspan></text>
  </g>
</svg>
07070100000012000081A40000000000000000000000016258A65C000003C2000000000000000000000000000000000000004300000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/index.adoc////
This is "asciidoctor", not plain "asciidoc".
https://asciidoctor.org/docs/user-manual/
////


////
This documentation is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a
letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
A copy of the license has been provided in the file documentation/LICENSE.
////

:Author: Nils Hilbricht
:iconfont-remote!:
:!webfonts:
:revnumber: 1.6.0

= New Session Manager Documentation

* link:https://github.com/jackaudio/new-session-manager[Sourcecode]
* link:https://github.com/jackaudio/new-session-manager/issues[Bug and Issue Tracker]
* link:api/index.html[API] document that describes all OSC Messages
* link:http://non.tuxfamily.org/session-manager/doc/MANUAL.html[Legacy-GUI Manual]. The original Non-Session-Manager GUI manual is still valid.


include::readme-01.md[]
07070100000013000081A40000000000000000000000016258A65C000004F2000000000000000000000000000000000000004400000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/jackpatch.1.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.49.1.
.TH JACKPATCH "1" "April 2022" "jackpatch Version 1.6.0" "User Commands"
.SH NAME
jackpatch \- manual page for jackpatch Version 1.6.0
.SH DESCRIPTION
jackpatch \- Remember and restore the JACK Audio Connection Kit Graph in NSM
.PP
It is intended as module for the 'New Session Manager' and only communicates
over OSC in an NSM\-Session.
.PP
It has limited standalone functionality for testing and debugging, such as:
.SS "Usage:"
.TP
jackpatch [filename]
Restore a snapshot from \fB\-\-save\fR and monitor.
.IP
jackpatch \fB\-\-help\fR
.SH OPTIONS
.TP
\fB\-\-help\fR
Show this screen and exit
.TP
\fB\-\-version\fR
Show version and exit
.TP
\fB\-\-save\fR
Save current connection snapshot to file and exit
.SH "REPORTING BUGS"
https://github.com/jackaudio/new-session-manager/issues
.SH COPYRIGHT
up to 2020:
Jonathan Moore Liles https://non.tuxfamily.org/

from 2020:
Nils Hilbricht et al. https://new-session-manager.jackaudio.org
.SH "SEE ALSO"
The  full  documentation for NSM is maintained as html site in your systems doc-dir.
For example:
    xdg-open file:///usr/share/doc/new-session-manager/index.html

The documentation can also be found online https://new-session-manager.jackaudio.org
07070100000014000081A40000000000000000000000016258A65C000002D3000000000000000000000000000000000000004600000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/jackpatch.h2m
[name]
jackpatch - JACK Audio Connection Kit environment saver for the "New Session Manager"

[usage]
jackapatch is a module for "New Session Manager".
It only communicates over OSC in an NSM-Session and has no standalone functionality.

[Reporting bugs]
https://github.com/jackaudio/new-session-manager/issues

[copyright]
up to 2020:
Jonathan Moore Liles https://non.tuxfamily.org/

from 2020:
Nils Hilbricht et al. https://new-session-manager.jackaudio.org

[see also]
The  full  documentation for NSM is maintained as html site in your systems doc-dir.
For example:
    xdg-open file:///usr/share/doc/new-session-manager/index.html

The documentation can also be found online https://new-session-manager.jackaudio.org
07070100000015000081A40000000000000000000000016258A65C000001E5000000000000000000000000000000000000004B00000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/manpage-common.h2m
[Reporting bugs]
https://github.com/jackaudio/new-session-manager/issues

[copyright]
up to 2020:
Jonathan Moore Liles https://non.tuxfamily.org/

from 2020:
Nils Hilbricht et al. https://new-session-manager.jackaudio.org

[see also]
The  full  documentation for NSM is maintained as html site in your systems doc-dir.
For example:
    xdg-open file:///usr/share/doc/new-session-manager/index.html

The documentation can also be found online https://new-session-manager.jackaudio.org
07070100000016000081A40000000000000000000000016258A65C000004BC000000000000000000000000000000000000004E00000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/non-session-manager.1.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.49.1.
.TH NSM-LEGACY-GUI "1" "April 2022" "nsm-legacy-gui Version 1.6.0" "User Commands"
.SH NAME
nsm-legacy-gui \- manual page for nsm-legacy-gui Version 1.6.0
.SH DESCRIPTION
nsm\-legacy\-gui \- FLTK GUI for the 'New Session Manager'
.SS "Usage:"
.IP
nsm\-legacy\-gui
nsm\-legacy\-gui \fB\-\-help\fR
.SH OPTIONS
.TP
\fB\-\-help\fR
Show this screen
.TP
\fB\-\-nsm\-url\fR url
Connect to a running nsmd [Example: osc.udp://mycomputer.localdomain:38356/].
.TP
\fB\-\-\fR
Everything after \fB\-\-\fR will be given to nsmd as server options. See nsmd \fB\-\-help\fR .
.PP
For backwards compatibility this executable also exist as symlink 'non\-session\-manager'
.SH "REPORTING BUGS"
https://github.com/jackaudio/new-session-manager/issues
.SH COPYRIGHT
up to 2020:
Jonathan Moore Liles https://non.tuxfamily.org/

from 2020:
Nils Hilbricht et al. https://new-session-manager.jackaudio.org
.SH "SEE ALSO"
The  full  documentation for NSM is maintained as html site in your systems doc-dir.
For example:
    xdg-open file:///usr/share/doc/new-session-manager/index.html

The documentation can also be found online https://new-session-manager.jackaudio.org
07070100000017000081A40000000000000000000000016258A65C000004BC000000000000000000000000000000000000004900000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/nsm-legacy-gui.1.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.49.1.
.TH NSM-LEGACY-GUI "1" "April 2022" "nsm-legacy-gui Version 1.6.0" "User Commands"
.SH NAME
nsm-legacy-gui \- manual page for nsm-legacy-gui Version 1.6.0
.SH DESCRIPTION
nsm\-legacy\-gui \- FLTK GUI for the 'New Session Manager'
.SS "Usage:"
.IP
nsm\-legacy\-gui
nsm\-legacy\-gui \fB\-\-help\fR
.SH OPTIONS
.TP
\fB\-\-help\fR
Show this screen
.TP
\fB\-\-nsm\-url\fR url
Connect to a running nsmd [Example: osc.udp://mycomputer.localdomain:38356/].
.TP
\fB\-\-\fR
Everything after \fB\-\-\fR will be given to nsmd as server options. See nsmd \fB\-\-help\fR .
.PP
For backwards compatibility this executable also exist as symlink 'non\-session\-manager'
.SH "REPORTING BUGS"
https://github.com/jackaudio/new-session-manager/issues
.SH COPYRIGHT
up to 2020:
Jonathan Moore Liles https://non.tuxfamily.org/

from 2020:
Nils Hilbricht et al. https://new-session-manager.jackaudio.org
.SH "SEE ALSO"
The  full  documentation for NSM is maintained as html site in your systems doc-dir.
For example:
    xdg-open file:///usr/share/doc/new-session-manager/index.html

The documentation can also be found online https://new-session-manager.jackaudio.org
07070100000018000081A40000000000000000000000016258A65C0000047A000000000000000000000000000000000000004800000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/nsm-proxy-gui.1.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.49.1.
.TH NSM-PROXY-GUI "1" "April 2022" "nsm-proxy-gui Version 1.6.0" "User Commands"
.SH NAME
nsm-proxy-gui \- manual page for nsm-proxy-gui Version 1.6.0
.SH DESCRIPTION
nsm\-proxy\-gui \- GUI for nsm\-proxy, a wrapper for executables without direct NSM\-Support.
.SS "Usage:"
.IP
nsm\-proxy\-gui \fB\-\-help\fR
nsm\-proxy\-gui \fB\-\-connect\-to\fR
.SH OPTIONS
.TP
\fB\-\-help\fR
Show this screen
.TP
\fB\-\-connect\-to\fR
Connect to running nsm\-proxy
.PP
nsmd\-proxy\-gui is usually not called by the user directly,
but autostarted when nsm\-proxy is added to a session (through a GUI).
.SH "REPORTING BUGS"
https://github.com/jackaudio/new-session-manager/issues
.SH COPYRIGHT
up to 2020:
Jonathan Moore Liles https://non.tuxfamily.org/

from 2020:
Nils Hilbricht et al. https://new-session-manager.jackaudio.org
.SH "SEE ALSO"
The  full  documentation for NSM is maintained as html site in your systems doc-dir.
For example:
    xdg-open file:///usr/share/doc/new-session-manager/index.html

The documentation can also be found online https://new-session-manager.jackaudio.org
07070100000019000081A40000000000000000000000016258A65C000003EC000000000000000000000000000000000000004400000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/nsm-proxy.1.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.49.1.
.TH NSM-PROXY "1" "April 2022" "nsm-proxy Version 1.6.0" "User Commands"
.SH NAME
nsm-proxy \- manual page for nsm-proxy Version 1.6.0
.SH DESCRIPTION
nsm\-proxy \- Wrapper for executables without direct NSM\-Support.
.PP
It is a module for the 'New Session Manager' and only communicates
over OSC in an NSM\-Session and has no standalone functionality.
.SS "Usage:"
.IP
nsm\-proxy \fB\-\-help\fR
.SH OPTIONS
.TP
\fB\-\-help\fR
Show this screen
.SH "REPORTING BUGS"
https://github.com/jackaudio/new-session-manager/issues
.SH COPYRIGHT
up to 2020:
Jonathan Moore Liles https://non.tuxfamily.org/

from 2020:
Nils Hilbricht et al. https://new-session-manager.jackaudio.org
.SH "SEE ALSO"
The  full  documentation for NSM is maintained as html site in your systems doc-dir.
For example:
    xdg-open file:///usr/share/doc/new-session-manager/index.html

The documentation can also be found online https://new-session-manager.jackaudio.org
0707010000001A000081A40000000000000000000000016258A65C000005F6000000000000000000000000000000000000003F00000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/nsmd.1.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.49.1.
.TH NSMD "1" "April 2022" "nsmd Version 1.6.0" "User Commands"
.SH NAME
nsmd \- manual page for nsmd Version 1.6.0
.SH DESCRIPTION
nsmd \- Daemon and server for the 'New Session Manager'
.SS "Usage:"
.IP
nsmd
nsmd \fB\-\-help\fR
nsmd \fB\-\-version\fR
.SH OPTIONS
.TP
\fB\-\-help\fR
Show this screen
.TP
\fB\-\-version\fR
Show version
.TP
\fB\-\-osc\-port\fR portnum
OSC port number [Default: provided by system].
.TP
\fB\-\-session\-root\fR path
Base path for sessions [Default: $XDG_DATA_HOME/nsm/].
.TP
\fB\-\-load\-session\fR name
Load existing session [Example: "My Song"].
.TP
\fB\-\-gui\-url\fR url
Connect to running legacy\-gui [Example: osc.udp://mycomputer.localdomain:38356/].
.TP
\fB\-\-detach\fR
Detach from console.
.TP
\fB\-\-quiet\fR
Suppress messages except warnings and errors.
.PP
nsmd can be run headless with existing sessions. To create new ones it is recommended to use a GUI
such as nsm\-legacy\-gui (included) or Agordejo (separate package)
.SH "REPORTING BUGS"
https://github.com/jackaudio/new-session-manager/issues
.SH COPYRIGHT
up to 2020:
Jonathan Moore Liles https://non.tuxfamily.org/

from 2020:
Nils Hilbricht et al. https://new-session-manager.jackaudio.org
.SH "SEE ALSO"
The  full  documentation for NSM is maintained as html site in your systems doc-dir.
For example:
    xdg-open file:///usr/share/doc/new-session-manager/index.html

The documentation can also be found online https://new-session-manager.jackaudio.org
0707010000001B000081A40000000000000000000000016258A65C00000016000000000000000000000000000000000000004500000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/readme-00.md# New Session Manager
0707010000001C000081A40000000000000000000000016258A65C000009D0000000000000000000000000000000000000004500000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/readme-01.md
## Introduction

New Session Manager (NSM) is a tool to assist music production by grouping standalone programs into
sessions. Your workflow becomes easy to manage, robust and fast by leveraging the full potential of
cooperative applications.

NSM continues to be free in every sense of the word: free of cost, free to share and use, free of
spyware or ads, free-and-open-source.

You can create a session, or project, add programs to it and then use commands to save, start/stop,
hide/show all programs at once, or individually. At a later date you can then re-open the session
and continue where you left off.

All files belonging to the session will be saved in the same directory.

If you are a user (and not a programmer or packager) everything you need is to install NSM
through your distributions package manager and, highly recommended, Agordejo as a GUI (see below).

To learn NSM you don't need to know the background information from our documentation, which
is aimed at developers that want to implement NSM support in their programs. Learn the GUI,
not the server and protocol.


## Bullet Points
* Drop-In replacement for the non-session-manager daemon nsmd and tools (e.g. jackpatch)
* Simple and hassle-free build system to make packaging easy
* Possibility to react to sensible bug fixes that would not have been integrated original nsmd
* Stay upwards and downwards compatible with original nsmd
* Conservative and hesitant in regards to new features and behaviour-changes, but possible in principle
* Keep the session-manager separate from the other NON* tools Mixer, Sequencer and Timeline.
* Protect nsmd from vanishing from the internet one day.
* The goal is to become the de-facto standard music session manager for Linux distributions

## User Interface
It is highly recommended to use Agordejo ( https://www.laborejo.org/agordejo/ ) as graphical
user interface. In fact, if you install Agordejo in your distribution it will install NSM as
dependency and you don't need to do anything yourself with this software package.

This repository also contains the legacy FLTK interface simply called `nsm-legacy-gui`,
symlinked to `non-session-manager` for backwards compatibility. (e.g. autostart scripts etc.)

## Supported Clients

While NSM can start and stop any program it only becomes convenient if clients specifically
implement support. This enables saving and hiding the GUI, amongst other features.
Documentation and tutorials for software-developers will be added at a later date.
0707010000001D000081A40000000000000000000000016258A65C00000CC9000000000000000000000000000000000000004500000000new-session-manager-1.6.0+git.20220415.0f6719c/docs/src/readme-02.md
## Documentation and Manual

Our documentation contains the API specification for the NSM protocol, which is the central document
if you want to add NSM support to your own application.

You can find html documentation installed to your systems SHARE dir or docs/out/index.html in this
repository.
There is also an online version https://jackaudio.github.io/new-session-manager/

We also provide a set of manpages for each executable (see Build).


## Fork and License
This is a fork of non-session-manager, by Jonathan Moore Liles <male@tuxfamily.net> http://non.tuxfamily.org/
which was released under the GNU GENERAL PUBLIC LICENSE  Version 2, June 1991.

All files, except nsm.h kept in this fork were GPL "version 2 of the License, or (at your
option) any later version."

`nsm.h` is licensed under the ISC License.

New-Session-Manager changed the license to GNU GENERAL PUBLIC LICENSE, Version 3, 29 June 2007.
See file COPYING

Documentation in docs/ is licensed Creative Commons CC-By-Sa.
It consist of mostly generated files and snippet files which do not have a license header for
technical reasons.
All original documentation source files are CC-By-Sa Version v4.0 (see file docs/src/LICENSE),
the source file docs/src/api/index.adoc is a derived work from NON-Session-Managers API file which
is licensed CC-By-Sa v2.5. Therefore our derived API document is also CC-By-Sa v2.5
(see files docs/src/api/readme.txt and docs/src/api/LICENSE)


## Build
The build system is meson.

This is a software package that will compile and install multiple executables:
* `nsmd`, the daemon or server itself. It is mandatory.
  * It has no GUI.
  * Dependency is `liblo`, the OSC library.
* `jackpatch`, NSM client to save and remember JACK connections.
  * It has no GUI.
  * Dependencies are `JACK Audio Connection Kit` and `liblo`, the OSC library.
  * Can be deactivated (see below) `-Djackpatch=false`
* `nsm-legacy-gui`, Legacy GUI for the user
  * Formerly known as "non-session-manager"
  * Dependencies are `FLTK`>=v1.3.0 and `liblo`, the OSC library.
  * Can be deactivated (see below) `-Dlegacy-gui=false`
* `nsm-proxy`, NSM GUI Client to run any program without direct NSM support
  * Dependencies are `FLTK`>=v1.3.0, `fluid` (FLTK Editor/compiler, maybe in the same package as FLTK, maybe not) and `liblo`, the OSC library.
  * Can be deactivated (see below) `-Dnsm-proxy=false`


```
meson build --prefix=/usr
#or disable individual build targets:
#meson build --prefix=/usr -Dlegacy-gui=false -Dnsm-proxy=false -Djackpatch=false
cd build && ninja
sudo ninja install
```

Optionally you can skip `sudo ninja install` and run all executables from the build-dir.
In this case you need to add the build-dir to your PATH environment variable so that the tools
can find each other.

## Names of Executable Files and Symlinks

Some distributions (and possibly local laws) prevent a forked software project from creating
executable files under the same name, if the name itself is an original work subject to copyright,
which it arguably is for the "NON-"-suite. Therefore New Session Manager renamed
`non-session-manager` to `nsm-legacy-gui`. Installing will also create a symlink to
`non-session-manager` for backwards compatibility. (e.g. autostart scripts etc.).
0707010000001E000041ED0000000000000000000000046258A65C00000000000000000000000000000000000000000000003600000000new-session-manager-1.6.0+git.20220415.0f6719c/extras0707010000001F000081A40000000000000000000000016258A65C00000222000000000000000000000000000000000000004000000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/README.md# New Session Manager - Extras

Each subdirectory in /extras holds additional libraries, software, documentation etc.

They are included for convenience, e.g. the library pynsm is useful for client-programmers,
and also developed by the same author as New-Session-Manager.

Each "extra" is standalone regarding license and build process. They are not build or installed
through nsm(d) meson build. The main programs in this repository do not depend on files in /extra in
any way. From a technical point of view `/extras` could be safely deleted.
07070100000020000041ED0000000000000000000000026258A65C00000000000000000000000000000000000000000000003C00000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/nsm.h07070100000021000081A40000000000000000000000016258A65C000002CE000000000000000000000000000000000000004400000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/nsm.h/COPYINGhttps://www.isc.org/licenses/

Permission to use, copy, modify, and/or distribute this software for any purpose with or without
fee is hereby granted, provided that the above copyright notice and this permission notice appear
in all copies.

THE SOFTWARE IS PROVIDED “AS IS” AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE
FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
07070100000022000081A40000000000000000000000016258A65C00005E89000000000000000000000000000000000000004200000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/nsm.h/nsm.h
/*******************************************A******************************/
/* Copyright (C) 2012 Jonathan Moore Liles                               */
/* Copyright (C) 2020- Nils Hilbricht                                    */
/*                                                                       */
/* Permission to use, copy, modify, and/or distribute this software for  */
/* any purpose with or without fee is hereby granted, provided that the  */
/* above copyright notice and this permission notice appear in all       */
/* copies.                                                               */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL         */
/* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED         */
/* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE      */
/* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL  */
/* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR */
/* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER        */
/* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR      */
/* PERFORMANCE OF THIS SOFTWARE.                                         */
/*************************************************************************/


/*************************************************************************/
/* A simple, callback based C API for NSM clients.                        */
/*                                                                        */
/* Simplified Example:                                                    */
/*                                                                        */
/* #include "nsm.h"                                                       */
/*                                                                        */
/* static nsm_client_t *nsm = 0;                                          */
/* static int wait_nsm = 1;                                               */
/*                                                                        */
/* int                                                                    */
/* cb_nsm_open ( const char *save_file_path,  //See API Docs 2.2.2        */
/*               const char *display_name,  //Not useful                  */
/*               const char *client_id,    //Use as JACK Client Name      */
/*               char **out_msg,                                          */
/*               void *userdata )                                         */
/* {                                                                      */
/*         do_open_stuff(); //Your own function                           */
/*         wait_nsm = 0;                                                  */
/*         return ERR_OK;                                                 */
/* }                                                                      */
/*                                                                        */
/* int                                                                    */
/* cb_nsm_save ( char **out_msg,                                          */
/*               void *userdata )                                         */
/* {                                                                      */
/*     do_save_stuff(); //Your own function                               */
/*     return ERR_OK;                                                     */
/* }                                                                      */
/*                                                                        */
/* void                                                                   */
/* cb_nsm_show ( void *userdata )                                         */
/* {                                                                      */
/*     do_show_ui();  //Your own function                                 */
/*     nsm_send_is_shown ( nsm );                                         */
/* }                                                                      */
/*                                                                        */
/* void                                                                   */
/* cb_nsm_hide ( void *userdata )                                         */
/* {                                                                      */
/*     do_hide_ui(); //Your own function                                  */
/*     nsm_send_is_hidden ( nsm );                                        */
/* }                                                                      */
/*                                                                        */
/* gboolean                                                               */
/* poll_nsm()                                                             */
/* {                                                                      */
/*     if ( nsm )                                                         */
/*     {                                                                  */
/*         nsm_check_nowait( nsm );                                       */
/*         return true;                                                   */
/*     }                                                                  */
/*     return false;                                                      */
/* }                                                                      */
/*                                                                        */
/* int main( int argc, char **argv )                                      */
/* {                                                                      */
/*     const char *nsm_url = getenv( "NSM_URL" );                         */
/*                                                                        */
/*     if ( nsm_url )                                                     */
/*     {                                                                  */
/*         nsm = nsm_new();                                               */
/*                                                                        */
/*         nsm_set_open_callback( nsm, cb_nsm_open, 0 );                  */
/*         nsm_set_save_callback( nsm, cb_nsm_save, 0 );                  */
/*                                                                        */
/*         if ( 0 == nsm_init( nsm, nsm_url ) )                           */
/*         {                                                              */
/*             nsm_send_announce( nsm, "FOO", ":optional-gui:", argv[0] );*/
/*                                                                        */
/* ********************************************************************** */
/*        This will block for at most 100 sec and                         */
/*        waiting for the NSM server open callback.                       */
/*        DISCLAIMER: YOU MAY NOT NEED TO DO THAT.                        */
/* ********************************************************************** */
/*                                                                        */
/*             int timeout = 0;                                           */
/*             while ( wait_nsm )                                         */
/*             {                                                          */
/*                 nsm_check_wait( nsm, 500 );                            */
/*                 timeout += 1;                                          */
/*                 if ( timeout > 200 )                                   */
/*                     exit ( 1 );                                        */
/*             }                                                          */
/*                                                                        */
/* ********************************************************************** */
/*        This will check if the server support optional-gui              */
/*        and connect the callbacks when support is found.                */
/*        If you don't use the above blocking block                       */
/*        this could be done in cb_nsm_open() as well.                    */
/*        DISCLAIMER: YOU MAY NOT NEED TO DO THAT.                        */
/* ********************************************************************** */
/*                                                                        */
/*             if ( strstr( nsm_get_session_manager_features ( nsm ),     */
/*                  ":optional-gui:" ) )                                  */
/*             {                                                          */
/*                 nsm_set_show_callback( nsm, cb_nsm_show, 0 );          */
/*                 nsm_set_hide_callback( nsm, cb_nsm_hide, 0 );          */
/*             }                                                          */
/*                                                                        */
/* ********************************************************************** */
/*                                                                        */
/*             do_timeout_add( 200, poll_nsm, Null ); //Your own function */
/*         }                                                              */
/*         else                                                           */
/*         {                                                              */
/*             nsm_free( nsm );                                           */
/*             nsm = 0;                                                   */
/*         }                                                              */
/*     }                                                                  */
/* }                                                                      */
/**************************************************************************/

#ifndef _NSM_H
#define _NSM_H

#define NSM_API_VERSION_MAJOR 1
#define NSM_API_VERSION_MINOR 0

#include <lo/lo.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

typedef void * nsm_client_t;
typedef int (nsm_open_callback)( const char *name, const char *display_name, const char *client_id, char **out_msg, void *userdata );
typedef int (nsm_save_callback)( char **out_msg, void *userdata );
typedef void (nsm_show_gui_callback)( void *userdata );
typedef void (nsm_hide_gui_callback)( void *userdata );
typedef void (nsm_active_callback)( int b, void *userdata );
typedef void (nsm_session_is_loaded_callback)( void *userdata );
typedef int (nsm_broadcast_callback)( const char *, lo_message m, void *userdata );

#define _NSM() ((struct _nsm_client_t*)nsm)

#define NSM_EXPORT __attribute__((unused)) static

/* private parts */
struct _nsm_client_t
{
    const char *nsm_url;

    lo_server _server;
    lo_server_thread _st;
    lo_address nsm_addr;

    int nsm_is_active;
    char *nsm_client_id;
    char *_session_manager_name;
    char *_session_manager_features;

    nsm_open_callback *open;
    void *open_userdata;

    nsm_save_callback *save;
    void *save_userdata;

    nsm_show_gui_callback *show;
    void *show_userdata;

    nsm_hide_gui_callback *hide;
    void *hide_userdata;

    nsm_active_callback *active;
    void *active_userdata;

    nsm_session_is_loaded_callback *session_is_loaded;
    void *session_is_loaded_userdata;

    nsm_broadcast_callback *broadcast;
    void *broadcast_userdata;
};

enum
{
    ERR_OK = 0,
    ERR_GENERAL    = -1,
    ERR_INCOMPATIBLE_API = -2,
    ERR_BLACKLISTED      = -3,
    ERR_LAUNCH_FAILED    = -4,
    ERR_NO_SUCH_FILE     = -5,
    ERR_NO_SESSION_OPEN  = -6,
    ERR_UNSAVED_CHANGES  = -7,
    ERR_NOT_NOW          = -8
};

NSM_EXPORT
int
nsm_is_active ( nsm_client_t *nsm )
{
    return _NSM()->nsm_is_active;
}

NSM_EXPORT
const char *
nsm_get_session_manager_name ( nsm_client_t *nsm )
{
    return _NSM()->_session_manager_name;
}

NSM_EXPORT
const char *
nsm_get_session_manager_features ( nsm_client_t *nsm )
{
    return _NSM()->_session_manager_features;
}

NSM_EXPORT
nsm_client_t *
nsm_new ( void )
{
    struct _nsm_client_t *nsm = (struct _nsm_client_t*)malloc( sizeof( struct _nsm_client_t ) );

    nsm->nsm_url = 0;

    nsm->nsm_is_active = 0;
    nsm->nsm_client_id = 0;

    nsm->_server = 0;
    nsm->_st = 0;
    nsm->nsm_addr = 0;
    nsm->_session_manager_name = 0;
    nsm->_session_manager_features = 0;

    nsm->open = 0;
    nsm->save = 0;
    nsm->show = 0;
    nsm->hide = 0;
    nsm->active = 0;
    nsm->session_is_loaded = 0;
    nsm->broadcast = 0;

    return (nsm_client_t *)nsm;
}

/*******************************************/
/* CLIENT TO SERVER INFORMATIONAL MESSAGES */
/*******************************************/

NSM_EXPORT
void
nsm_send_is_dirty ( nsm_client_t *nsm )
{
    if ( _NSM()->nsm_is_active )
        lo_send_from( _NSM()->nsm_addr, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/client/is_dirty", "" );
}

NSM_EXPORT
void
nsm_send_is_clean ( nsm_client_t *nsm )
{
    if ( _NSM()->nsm_is_active )
        lo_send_from( _NSM()->nsm_addr, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/client/is_clean", "" );
}

NSM_EXPORT
void
nsm_send_is_shown ( nsm_client_t *nsm )
{
    if ( _NSM()->nsm_is_active )
        lo_send_from( _NSM()->nsm_addr, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/client/gui_is_shown", "" );
}

NSM_EXPORT
void
nsm_send_is_hidden ( nsm_client_t *nsm )
{
    if ( _NSM()->nsm_is_active )
        lo_send_from( _NSM()->nsm_addr, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" );
}

NSM_EXPORT
void
nsm_send_progress ( nsm_client_t *nsm, float p )
{
    if ( _NSM()->nsm_is_active )
        lo_send_from( _NSM()->nsm_addr, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/client/progress", "f", p );
}

NSM_EXPORT
void
nsm_send_message ( nsm_client_t *nsm, int priority, const char *msg )
{
   if ( _NSM()->nsm_is_active )
       lo_send_from( _NSM()->nsm_addr, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/client/message", "is", priority, msg );
}

NSM_EXPORT void
nsm_send_announce ( nsm_client_t *nsm, const char *app_name, const char *capabilities, const char *process_name )
{
    lo_address to = lo_address_new_from_url( _NSM()->nsm_url );

    if ( ! to )
    {
        fprintf( stderr, "NSM: Bad address!" );
        return;
    }

    int pid = (int)getpid();

    lo_send_from( to, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/server/announce", "sssiii",
                  app_name,
                  capabilities,
                  process_name,
                  NSM_API_VERSION_MAJOR,
                  NSM_API_VERSION_MINOR,
                  pid );

    lo_address_free( to );
}

NSM_EXPORT void
nsm_send_broadcast ( nsm_client_t *nsm, lo_message msg )
{
   if ( _NSM()->nsm_is_active )
       lo_send_message_from( _NSM()->nsm_addr, _NSM()->_server, "/nsm/server/broadcast", msg );
}



NSM_EXPORT
void
nsm_check_wait ( nsm_client_t *nsm, int timeout )
{
    if ( lo_server_wait( _NSM()->_server, timeout ) )
        while ( lo_server_recv_noblock( _NSM()->_server, 0 ) ) {}
}

NSM_EXPORT
void
nsm_check_nowait (nsm_client_t *nsm )
{
    nsm_check_wait( nsm, 0 );
}


NSM_EXPORT
void
nsm_thread_start ( nsm_client_t *nsm )
{
    lo_server_thread_start( _NSM()->_st );
}


NSM_EXPORT
void
nsm_thread_stop ( nsm_client_t *nsm )
{
    lo_server_thread_stop( _NSM()->_st );
}



NSM_EXPORT void
nsm_free ( nsm_client_t *nsm )
{
    if ( _NSM()->_st )
        nsm_thread_stop( nsm );

    if ( _NSM()->_st )
        lo_server_thread_free( _NSM()->_st );
    else
        lo_server_free( _NSM()->_server );

    lo_address_free(_NSM()->nsm_addr);
    free(_NSM()->nsm_client_id);
    free(_NSM()->_session_manager_name);
    free(_NSM()->_session_manager_features);

    free( _NSM() );
}

/*****************/
/* SET CALLBACKS */
/*****************/

NSM_EXPORT
void
nsm_set_open_callback( nsm_client_t *nsm, nsm_open_callback *open_callback, void *userdata )
{
    _NSM()->open = open_callback;
    _NSM()->open_userdata = userdata;
}

NSM_EXPORT
void
nsm_set_save_callback( nsm_client_t *nsm, nsm_save_callback *save_callback, void *userdata )
{
    _NSM()->save = save_callback;
    _NSM()->save_userdata = userdata;

}

NSM_EXPORT
void
nsm_set_show_callback( nsm_client_t *nsm, nsm_show_gui_callback *show_callback, void *userdata )
{
    _NSM()->show = show_callback;
    _NSM()->show_userdata = userdata;
}

NSM_EXPORT
void
nsm_set_hide_callback( nsm_client_t *nsm, nsm_hide_gui_callback *hide_callback, void *userdata )
{
    _NSM()->hide = hide_callback;
    _NSM()->hide_userdata = userdata;
}

NSM_EXPORT
void
nsm_set_active_callback( nsm_client_t *nsm, nsm_active_callback *active_callback, void *userdata )
{
    _NSM()->active = active_callback;
    _NSM()->active_userdata = userdata;
}

NSM_EXPORT
void
nsm_set_session_is_loaded_callback( nsm_client_t *nsm, nsm_session_is_loaded_callback *session_is_loaded_callback, void *userdata )
{
    _NSM()->session_is_loaded = session_is_loaded_callback;
    _NSM()->session_is_loaded_userdata = userdata;
}


NSM_EXPORT
void
nsm_set_broadcast_callback( nsm_client_t *nsm, nsm_broadcast_callback *broadcast_callback, void *userdata )
{
    _NSM()->broadcast = broadcast_callback;
    _NSM()->broadcast_userdata = userdata;
}



/****************/
/* OSC HANDLERS */
/****************/

#undef OSC_REPLY
#undef OSC_REPLY_ERR

#define OSC_REPLY( value ) lo_send_from( ((struct _nsm_client_t*)user_data)->nsm_addr, ((struct _nsm_client_t*)user_data)->_server, LO_TT_IMMEDIATE, "/reply", "ss", path, value )

#define OSC_REPLY_ERR( errcode, value ) lo_send_from( ((struct _nsm_client_t*)user_data)->nsm_addr, ((struct _nsm_client_t*)user_data)->_server, LO_TT_IMMEDIATE, "/error", "sis", path, errcode, value )


NSM_EXPORT int _nsm_osc_open ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    (void) types;
    (void) argc;
    (void) msg;

    char *out_msg = NULL;

    struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data;

    nsm->nsm_client_id = strdup( &argv[2]->s );

    if ( ! nsm->open )
        return 0;

    int r = nsm->open( &argv[0]->s, &argv[1]->s, &argv[2]->s, &out_msg, nsm->open_userdata );

    if ( r )
        OSC_REPLY_ERR( r, ( out_msg ? out_msg : "") );
    else
        OSC_REPLY( "OK" );

    if ( out_msg )
        free( out_msg );

    return 0;
}

NSM_EXPORT int _nsm_osc_save ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    (void) types;
    (void) argv;
    (void) argc;
    (void) msg;

    char *out_msg = NULL;

    struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data;

    if ( ! nsm->save )
        return 0;

    int r = nsm->save(&out_msg, nsm->save_userdata );

    if ( r )
        OSC_REPLY_ERR( r, ( out_msg ? out_msg : "") );
    else
        OSC_REPLY( "OK" );

    if ( out_msg )
        free( out_msg );

    return 0;
}

NSM_EXPORT int _nsm_osc_announce_reply ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    (void) path;
    (void) types;
    (void) argc;

    if ( strcmp( &argv[0]->s, "/nsm/server/announce" ) )
        return -1;

    struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data;

    fprintf( stderr, "NSM: Successfully registered. NSM says: %s", &argv[1]->s );

    nsm->nsm_is_active = 1;
    nsm->_session_manager_name = strdup( &argv[2]->s );
    nsm->_session_manager_features = strdup( &argv[3]->s );
    nsm->nsm_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ));

    if ( nsm->active )
        nsm->active( nsm->nsm_is_active, nsm->active_userdata );

    return 0;
}

NSM_EXPORT int _nsm_osc_error ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    (void) path;
    (void) types;
    (void) argc;
    (void) msg;

    if ( strcmp( &argv[0]->s, "/nsm/server/announce" ) )
        return -1;

    struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data;

    fprintf( stderr, "NSM: Failed to register with NSM server: %s", &argv[2]->s );

    nsm->nsm_is_active = 0;

    if ( nsm->active )
        nsm->active( nsm->nsm_is_active, nsm->active_userdata );

    return 0;
}

NSM_EXPORT int _nsm_osc_session_is_loaded ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    (void) path;
    (void) types;
    (void) argv;
    (void) argc;
    (void) msg;

    struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data;

    if ( ! nsm->session_is_loaded )
        return 0;

    nsm->session_is_loaded( nsm->session_is_loaded_userdata );

    return 0;
}

NSM_EXPORT int _nsm_osc_show ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{

    struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data;

    if ( ! nsm->show )
        return 0;

    nsm->show( nsm->show_userdata );

    return 0;
}

NSM_EXPORT int _nsm_osc_hide ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{

    struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data;

    if ( ! nsm->hide )
        return 0;

    nsm->hide( nsm->hide_userdata );

    return 0;
}

NSM_EXPORT int _nsm_osc_broadcast ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    (void) types;
    (void) argv;
    (void) argc;

    struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data;

    if ( ! nsm->broadcast )
        return 0;

    return nsm->broadcast( path, msg, nsm->broadcast_userdata );
}



NSM_EXPORT
int
nsm_init ( nsm_client_t *nsm, const char *nsm_url )
{
    _NSM()->nsm_url = nsm_url;

    lo_address addr = lo_address_new_from_url( nsm_url );
    int proto = lo_address_get_protocol( addr );
    lo_address_free( addr );

    _NSM()->_server = lo_server_new_with_proto( NULL, proto, NULL );

    if ( ! _NSM()->_server )
        return -1;

    lo_server_add_method( _NSM()->_server, "/error", "sis", _nsm_osc_error, _NSM() );
    lo_server_add_method( _NSM()->_server, "/reply", "ssss", _nsm_osc_announce_reply, _NSM() );
    lo_server_add_method( _NSM()->_server, "/nsm/client/open", "sss", _nsm_osc_open, _NSM() );
    lo_server_add_method( _NSM()->_server, "/nsm/client/save", "", _nsm_osc_save, _NSM() );
    lo_server_add_method( _NSM()->_server, "/nsm/client/session_is_loaded", "", _nsm_osc_session_is_loaded, _NSM() );
    lo_server_add_method( _NSM()->_server, "/nsm/client/show_optional_gui", "", _nsm_osc_show, _NSM() );
    lo_server_add_method( _NSM()->_server, "/nsm/client/hide_optional_gui", "", _nsm_osc_hide, _NSM() );
    lo_server_add_method( _NSM()->_server, NULL, NULL, _nsm_osc_broadcast, _NSM() );

    return 0;
}


NSM_EXPORT
int
nsm_init_thread ( nsm_client_t *nsm, const char *nsm_url )
{
    _NSM()->nsm_url = nsm_url;

    lo_address addr = lo_address_new_from_url( nsm_url );
    int proto = lo_address_get_protocol( addr );
    lo_address_free( addr );

    _NSM()->_st = lo_server_thread_new_with_proto( NULL, proto, NULL );
    _NSM()->_server = lo_server_thread_get_server( _NSM()->_st );

    if ( ! _NSM()->_server )
        return -1;

    lo_server_thread_add_method( _NSM()->_st, "/error", "sis", _nsm_osc_error, _NSM() );
    lo_server_thread_add_method( _NSM()->_st, "/reply", "ssss", _nsm_osc_announce_reply, _NSM() );
    lo_server_thread_add_method( _NSM()->_st, "/nsm/client/open", "sss", _nsm_osc_open, _NSM() );
    lo_server_thread_add_method( _NSM()->_st, "/nsm/client/save", "", _nsm_osc_save, _NSM() );
    lo_server_thread_add_method( _NSM()->_st, "/nsm/client/session_is_loaded", "", _nsm_osc_session_is_loaded, _NSM() );
    lo_server_thread_add_method( _NSM()->_st, "/nsm/client/show_optional_gui", "", _nsm_osc_show, _NSM() );
    lo_server_thread_add_method( _NSM()->_st, "/nsm/client/hide_optional_gui", "", _nsm_osc_hide, _NSM() );
    lo_server_thread_add_method( _NSM()->_st, NULL, NULL, _nsm_osc_broadcast, _NSM() );

    return 0;
}

#endif /* NSM_H */
07070100000023000041ED0000000000000000000000036258A65C00000000000000000000000000000000000000000000003C00000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/pynsm07070100000024000081A40000000000000000000000016258A65C00000469000000000000000000000000000000000000004400000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/pynsm/LICENSEMIT License

Copyright (c) since 2014: Laborejo Software Suite <info@laborejo.org>, All rights reserved.

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.
07070100000025000081A40000000000000000000000016258A65C0000196A000000000000000000000000000000000000004600000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/pynsm/README.md# pynsm
NSM/New Session Manager client library in Python - No dependencies except Python3.

PyNSMClient -  A New Session Manager Client-Library in one file.

Copyright (c) since 2014: Laborejo Software Suite <info@laborejo.org>, All rights reserved.

This library is licensed under the MIT license. Please check the file LICENSE for more information.

This library has no version numbers, or releases. It is a "rolling" git. Copy it into your source and update only if you need new features.

## Short Instructions
Before you start a word about control flow: NSM and any client, like this one, can be
considered automation and remote-control of GUI operations, normally done by the user. That means
this module needs to be included in your GUI code. The author personally creates the pynsm-object
as early as possible in his PyQt Mainwindow class.

Copy nsmclient.py to your own program and import and initialize it as early as possible (see below)
Then add nsmClient.reactToMessage to your existing event loop.

    from nsmclient import NSMClient
    nsmClient = NSMClient(prettyName = niceTitle, #will raise an error and exit if this example is not run from NSM.
        saveCallback = saveCallbackFunction,
        openOrNewCallback = openOrNewCallbackFunction,
        supportsSaveStatus = False,         # Change this to True if your program announces it's save status to NSM
        exitProgramCallback = exitCallbackFunction,
        hideGUICallback = None, #replace with your hiding function. You need to answer in your function with nsmClient.announceGuiVisibility(False)
        showGUICallback = None,  #replace with your showing function. You need to answer in your function with nsmClient.announceGuiVisibility(True)
        broadcastCallback = None,  #give a function that reacts to any broadcast by any other client.
        sessionIsLoadedCallback = None, #give a function that reacts to the one-time state when a session has fully loaded.
        loggingLevel = "info", #"info" for development or debugging, "error" for production. default is error.
        )


Don't forget to add nsmClient.reactToMessage to your event loop.

* Each of the callbacks "save", "open/new" and "exit" receive three parameters: ourPath, sessionName, ourClientNameUnderNSM.
* openOrNew gets called first. Init your jack client there with ourClientNameUnderNSM as name.
* exitProgramCallback is the place to gracefully exit your program, including jack-client closing.
* saveCallback gets called all the time. Use ourPath either as filename or as directory.
    * If you choose filename add an extension.
    * If you choose directory make sure that the filenames inside are static, no matter what project/session. The user must have no influence over file naming
* broadcastCallback receives five parameters. The three standard: ourPath, sessionName, ourClientNameUnderNSM. And additionally messagePath and listOfArguments. MessagePath is entirely program specific, the number and type of arguments depend on the sender.
* sessionIsLoadedCallback receives no parameters. It is ONLY send once, after the session is fully loaded. This is NOT the place to delay your announce. If you add your client to a running session (which must happen at some point) sessionLoaded will NOT get called.
* Additional callbacks are: hideGUICallback and showGUICallback. These receive no parameters and need to answer with the function: nsmClient.announceGuiVisibility(bool). That means you can decline show or hide, dependending on the state of your program.

The nsmClient object has methods and variables such as:

* nsmClient.ourClientNameUnderNSM
  * Always use this name for your program
* nsmClient.announceSaveStatus(False)
  * Announce your save status (dirty = False / clean = True), If your program sends those messages set supportsSaveStatus = True when intializing NSMClient with both hideGUICallback and showGUICallback
* nsmClient.sessionName
* nsmClient.ourOscUrl = osc.udp://{ip}:{port}/  Use this to broadcast your presence, to handshake communication between different programs
* nsmClient.announceGuiVisibility(bool)
  * Announce if your GUI is visible (True) or not (False). Only works if you initialized NSMClient with both hideGUICallback and showGUICallback. Don't forget to send it once for your state after starting your program.
* nsmcClient.changeLabel(prettyName)
  * Tell the GUI to append (prettyName) to our name. This is not saved by NSM but you need to send it yourself each startup.
* nsmClient.serverSendSaveToSelf()
  * A clean solution to use the nsm save callbacks from within your program (Ctrl+S or File->Save). No need for redundant save mechanism.
* nsmClient.serverSendExitToSelf()
  * A clean quit, without "client died unexpectedly". Use sys.exit() to exit your program in your nms-quit callback.
* nsmClient.importResource(filepath)
  * Use this to load external resources, for example a sample file. It links the sample file into the session dir, according to the NSM rules, and returns the path of the linked file.
* nsmClient.debugResetDataAndExit()
  * Deletes self.ourpath, which is the session save file or directory, recursively and exits the client. This is only meant for debugging and testing.

## Long Instructions
* Read and start example.py, then read and understand nsmclient.py. It requires PyQt5 to execute and a brain to read.
* There are several very minimal and basic clients in the directory `minimalClients/`
* For your own program read and learn the NSM API: http://non.tuxfamily.org/nsm/API.html
* The hard part about session management is not to use this lib or write your own but to make your program comply to the strict rules of session management.

## Additional Examples
More examples can be found in `/minimalClients`. This mimics a minimal, but functional program.
To actually run the programs and attach them to a running session execute the inluced file `source_me_with_port.bash <PORT>`
where <PORT> is the NSM port the server printed out after starting.

You can see that nsmclient.py is included in this dir again, to avoid redundancy only as symlink.
In your real program this would be the real file.

## Sources and Influences
* The New-Session-Manager by Jonathan Moore Liles <male@tuxfamily.org>: http://non.tuxfamily.org/nsm/
* New Session Manager by Nils Hilbricht et al  https://new-session-manager.jackaudio.org
* With help from code fragments from https://github.com/attwad/python-osc ( DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE v2 )
07070100000026000081ED0000000000000000000000016258A65C00001557000000000000000000000000000000000000005200000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/pynsm/exampleBoilerplate.py#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PyNSMClient -  A New Session Manager Client-Library in one file.

The Non-Session-Manager by Jonathan Moore Liles <male@tuxfamily.org>: http://non.tuxfamily.org/nsm/
New Session Manager by Nils Hilbricht et al  https://new-session-manager.jackaudio.org
With help from code fragments from https://github.com/attwad/python-osc ( DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE v2 )

MIT License

Copyright (c) since 2014: Laborejo Software Suite <info@laborejo.org>, All rights reserved.

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.
"""


from time import sleep              # main event loop at the bottom of this file
# nsm only
from nsmclient import NSMClient     # will raise an error and exit if this example is not run from NSM.

########################################################################
# General
########################################################################
niceTitle = "myNSMprogram"            # This is the name of your program. This will display in NSM and can be used in your save file

########################################################################
# Prepare the NSM Client
# This has to be done as the first thing because NSM needs to get the paths
# and may quit if NSM environment var was not found.
#
# This is also where to set up your functions that react to messages from NSM,
########################################################################
# Here are some variables you can use:
# ourPath               = Full path to your NSM save file/directory with serialized NSM extension
# ourClientNameUnderNSM = Your NSM save file/directory with serialized NSM extension and no path information
# sessionName           = The name of your session with no path information
########################################################################

def saveCallback(ourPath, sessionName, ourClientNameUnderNSM):
    # Put your code to save your config file in here
    print("saveCallback");


def openCallback(ourPath, sessionName, ourClientNameUnderNSM):
    # Put your code to open your config file in here
    print("openCallback");


def exitProgram(ourPath, sessionName, ourClientNameUnderNSM):
    """This function is a callback for NSM.
    We have a chance to close our clients and open connections here.
    If not nsmclient will just kill us no matter what
    """
    print("exitProgram");
    # Exit is done by NSM kill.


def showGUICallback():
    # Put your code that shows your GUI in here
    print("showGUICallback");
    nsmClient.announceGuiVisibility(isVisible=True)  # Inform NSM that the GUI is now visible. Put this at the end.


def hideGUICallback():
    # Put your code that hides your GUI in here
    print("hideGUICallback");
    nsmClient.announceGuiVisibility(isVisible=False)  # Inform NSM that the GUI is now hidden. Put this at the end.


nsmClient = NSMClient(prettyName = niceTitle,
                      saveCallback = saveCallback,
                      openOrNewCallback = openCallback,
                      showGUICallback = showGUICallback,  # Comment this line out if your program does not have an optional GUI
                      hideGUICallback = hideGUICallback,  # Comment this line out if your program does not have an optional GUI
                      supportsSaveStatus = False,         # Change this to True if your program announces it's save status to NSM
                      exitProgramCallback = exitProgram,
                      loggingLevel = "info", # "info" for development or debugging, "error" for production. default is error.
                      )

# If NSM did not start up properly the program quits here.

########################################################################
# If your project uses JACK, activate your client here
# You can use jackClientName or create your own
########################################################################
jackClientName = nsmClient.ourClientNameUnderNSM

########################################################################
# Start main program loop.
########################################################################

# showGUICallback()  # If you want your GUI to be shown by default, uncomment this line
print("Entering main loop")

while True:
    nsmClient.reactToMessage()  # Make sure this exists somewhere in your main loop
    # nsmClient.announceSaveStatus(False) # Announce your save status (dirty = False / clean = True)
    sleep(0.05)
07070100000027000081ED0000000000000000000000016258A65C00002A8D000000000000000000000000000000000000004900000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/pynsm/exampleQt.py#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PyNSMClient -  A New Session Manager Client-Library in one file.

The Non-Session-Manager by Jonathan Moore Liles <male@tuxfamily.org>: http://non.tuxfamily.org/nsm/
New Session Manager by Nils Hilbricht et al  https://new-session-manager.jackaudio.org
With help from code fragments from https://github.com/attwad/python-osc ( DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE v2 )

MIT License

Copyright (c) since 2014: Laborejo Software Suite <info@laborejo.org>, All rights reserved.

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.
"""

import sys #for qt app args.
from os import getpid #we use this as jack meta data
from PyQt5 import QtWidgets, QtCore

#jack only
import ctypes
from random import uniform #to generate samples between -1.0 and 1.0 for Jack.

#nsm only
from nsmclient import NSMClient


########################################################################
#Prepare the Qt Window
########################################################################
class Main(QtWidgets.QWidget):
    def __init__(self, qtApp):
        super().__init__()

        self.qtApp = qtApp
        self.layout = QtWidgets.QVBoxLayout()
        self.layout.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)
        self.setLayout(self.layout)

        niceTitle = "PyNSM v2 Example - JACK Noise"
        self.title = QtWidgets.QLabel("")
        self.saved = QtWidgets.QLabel("")
        self._value = QtWidgets.QSlider(orientation = 1) #horizontal
        self._value.setMinimum(0)
        self._value.setMaximum(100)
        self._value.setValue(50) #only visible for the first start.

        self.valueLabel = QtWidgets.QLabel("Noise Volume: " + str(self._value.value()))
        self._value.valueChanged.connect(lambda new: self.valueLabel.setText("Noise Volume: " + str(new)))

        self.layout.addWidget(self.title)
        self.layout.addWidget(self.saved)
        self.layout.addWidget(self._value)
        self.layout.addWidget(self.valueLabel)

        #Prepare the NSM Client
        #This has to be done as soon as possible because NSM provides paths and names for us.
        #and may quit if NSM environment var was not found.
        self.nsmClient = NSMClient(prettyName = niceTitle, #will raise an error and exit if this example is not run from NSM.
                      supportsSaveStatus = True,
                      saveCallback = self.saveCallback,
                      openOrNewCallback = self.openOrNewCallback,
                      exitProgramCallback = self.exitCallback,
                      loggingLevel = "info", #"info" for development or debugging, "error" for production. default is error.
                      )
        #If NSM did not start up properly the program quits here with an error message from NSM.
        #No JACK client gets created, no Qt window can be seen.


        self.title.setText("<b>" + self.nsmClient.ourClientNameUnderNSM +  "</b>")

        self.eventLoop = QtCore.QTimer()
        self.eventLoop.start(100) #10ms-20ms is smooth for "real time" feeling. 100ms is still ok.
        self.eventLoop.timeout.connect(self.nsmClient.reactToMessage)

        #self.show is called as the new/open callback.

    @property
    def value(self):
        return str(self._value.value())

    @value.setter
    def value(self, new):
        new = int(new)
        self._value.setValue(new)

    def saveCallback(self, ourPath, sessionName, ourClientNameUnderNSM): #parameters are filled in by NSM.
        if self.value:
            with open(ourPath, "w") as f:  #ourpath is taken as a filename here. We have this path name at our disposal and we know we only want one file. So we don't make a directory. This way we don't have to create a dir first.
                f.write(self.value)

    def openOrNewCallback(self, ourPath, sessionName, ourClientNameUnderNSM): #parameters are filled in by NSM.
        try:
            with open(ourPath, "r") as f:
                savedValue = f.read() #a string
                self.saved.setText("{}: {}".format(ourPath, savedValue))
                self.value = savedValue #internal casting to int. Sets the slider.
        except FileNotFoundError:
            self.saved.setText("{}: No save file found. Normal for first start.".format(ourPath))
        finally:
            self.show()

    def exitCallback(self, ourPath, sessionName, ourClientNameUnderNSM):
        """This function is a callback for NSM.
        We have a chance to close our clients and open connections here.
        If not nsmclient will just kill us no matter what
        """
        cjack.jack_remove_properties(ctypesJackClient, ctypesJackUuid) #clean our metadata
        cjack.jack_client_close(ctypesJackClient) #omitting this introduces problems. in Jack1 this would mute all jack clients for several seconds.
        exit() #or get SIGKILLed through NSM

    def closeEvent(self, event):
        """Qt likes to quits on its own. For example when the window manager closes the
        main window. Ignore that request and instead send a roundtrip through NSM"""
        self.nsmClient.serverSendExitToSelf()
        event.ignore()

#Prepare the window instance. Gets executed at the end of this file.
qtApp = QtWidgets.QApplication(sys.argv)
ourClient = Main(qtApp)

########################################################################
#Prepare the JACK Client
#We need the client name from NSM first.
########################################################################
cjack = ctypes.cdll.LoadLibrary("libjack.so.0")
clientName = ourClient.nsmClient.prettyName #the nsm client is in the qt instance here. But in your program it can be anywhere.
options = 0
status = None

class jack_client_t(ctypes.Structure):
    _fields_ = []
cjack.jack_client_open.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.POINTER(ctypes.c_int)]  #the two ints are enum and pointer to enum. #http://jackaudio.org/files/docs/html/group__ClientFunctions.html#gab8b16ee616207532d0585d04a0bd1d60
cjack.jack_client_open.restype = ctypes.POINTER(jack_client_t)
ctypesJackClient = cjack.jack_client_open(clientName.encode("ascii"), options, status)

#Create one output port
class jack_port_t(ctypes.Structure):
    _fields_ = []

JACK_DEFAULT_AUDIO_TYPE = "32 bit float mono audio".encode("ascii") #http://jackaudio.org/files/docs/html/types_8h.html
JACK_PORT_IS_OUTPUT = 0x2 #http://jackaudio.org/files/docs/html/types_8h.html
portname = "output".encode("ascii")

cjack.jack_port_register.argtypes = [ctypes.POINTER(jack_client_t), ctypes.c_char_p, ctypes.c_char_p, ctypes.c_ulong, ctypes.c_ulong] #http://jackaudio.org/files/docs/html/group__PortFunctions.html#ga3e21d145c3c82d273a889272f0e405e7
cjack.jack_port_register.restype = ctypes.POINTER(jack_port_t)
outputPort = cjack.jack_port_register(ctypesJackClient, portname,  JACK_DEFAULT_AUDIO_TYPE, JACK_PORT_IS_OUTPUT, 0)

cjack.jack_client_close.argtypes = [ctypes.POINTER(jack_client_t),]

#Create the callback
#http://jackaudio.org/files/docs/html/group__ClientCallbacks.html#gafb5ec9fb4b736606d676c135fb97888b

jack_nframes_t = ctypes.c_uint32
cjack.jack_port_get_buffer.argtypes = [ctypes.POINTER(jack_port_t), jack_nframes_t]
cjack.jack_port_get_buffer.restype = ctypes.POINTER(ctypes.c_float) #this is only valid for audio, not for midi. C Jack has a pointer to void here.

def pythonJackCallback(nframes, void): #types: jack_nframes_t (ctypes.c_uint32), pointer to void
    """http://jackaudio.org/files/docs/html/simple__client_8c.html#a01271cc6cf692278ae35d0062935d7ae"""
    out = cjack.jack_port_get_buffer(outputPort, nframes) #out should be a pointer to jack_default_audio_sample_t (float, ctypes.POINTER(ctypes.c_float))

    #For each required sample
    for i in range(nframes):
        factor = ourClient._value.value() / 100
        val =  ctypes.c_float(round(uniform(-0.5, 0.5) * factor, 10))
        out[i]= val

    return 0 # 0 on success, otherwise a non-zero error code, causing JACK to remove that client from the process() graph.

JACK_CALLBACK_TYPE = ctypes.CFUNCTYPE(ctypes.c_int, jack_nframes_t, ctypes.c_void_p) #the first parameter is the return type, the following are input parameters
callbackFunction = JACK_CALLBACK_TYPE(pythonJackCallback)

cjack.jack_set_process_callback.argtypes = [ctypes.POINTER(jack_client_t), JACK_CALLBACK_TYPE, ctypes.c_void_p]
cjack.jack_set_process_callback.restype = ctypes.c_uint32 #I think this is redundant since ctypes has int as default result type
cjack.jack_set_process_callback(ctypesJackClient, callbackFunction, 0)

#Ready. Activate the client.
cjack.jack_activate(ctypesJackClient)
#The Jack Processing functions gets called by jack in another thread. We just have to keep this program itself running. Qt does the job.


#Jack Metadata - Inform the jack server about our program. Optional but has benefits when used with other programs that rely on metadata.
#http://jackaudio.org/files/docs/html/group__Metadata.html
jack_uuid_t = ctypes.c_uint64
cjack.jack_set_property.argtypes = [ctypes.POINTER(jack_client_t), jack_uuid_t, ctypes.c_char_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_char_p)]  #client(we), subject/uuid,key,value/data,type
cjack.jack_remove_properties.argtypes = [ctypes.POINTER(jack_client_t), jack_uuid_t] #for cleaning up when the program stops. the jack server can do it in newer jack versions, but this is safer.

cjack.jack_get_uuid_for_client_name.argtypes = [ctypes.POINTER(jack_client_t), ctypes.c_char_p]
cjack.jack_get_uuid_for_client_name.restype = ctypes.c_char_p

ourJackUuid = cjack.jack_get_uuid_for_client_name(ctypesJackClient, clientName.encode("ascii"))
ourJackUuid = int(ourJackUuid.decode("UTF-8"))
ctypesJackUuid = jack_uuid_t(ourJackUuid)

cjack.jack_set_property(ctypesJackClient, ctypesJackUuid, ctypes.c_char_p(b"pid"), ctypes.c_char_p(str(getpid()).encode()), None)

##################
#Start everything
qtApp.exec_()
07070100000028000041ED0000000000000000000000026258A65C00000000000000000000000000000000000000000000004B00000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/pynsm/minimalClients07070100000029000081ED0000000000000000000000016258A65C000010A4000000000000000000000000000000000000005300000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/pynsm/minimalClients/base.py#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PyNSMClient -  A New Session Manager Client-Library in one file.

The Non-Session-Manager by Jonathan Moore Liles <male@tuxfamily.org>: http://non.tuxfamily.org/nsm/
New Session Manager by Nils Hilbricht et al  https://new-session-manager.jackaudio.org
With help from code fragments from https://github.com/attwad/python-osc ( DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE v2 )

MIT License

Copyright (c) since 2014: Laborejo Software Suite <info@laborejo.org>, All rights reserved.

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.
"""

#/nsm/server/announce  #A hack to get this into Agordejo launcher discovery


from nsmclient import NSMClient
import sys
from time import sleep
import os
from threading import Timer

sys.path.append(os.getcwd())

class BaseClient(object):

    def saveCallbackFunction(self, ourPath, sessionName, ourClientNameUnderNSM):
        print (__file__, "save")

    def openOrNewCallbackFunction(self, ourPath, sessionName, ourClientNameUnderNSM):
        print (__file__,"open/new")

    def exitCallbackFunction(self, ourPath, sessionName, ourClientNameUnderNSM):
        print (__file__, "quit")
        sys.exit()

    def broadcastCallbackFunction(self, ourPath, sessionName, ourClientNameUnderNSM, messagePath, listOfArguments):
        print (__file__, "broadcast")

    def event(self, nsmClient):
        pass

    def __init__(self, name, delayedFunctions=[], eventFunction=None):
        """delayedFunctions are a (timer delay in seconds, function call) list of tuples. They will
        be executed once.
        If the function is a string instead it will be evaluated in the BaseClient context,
        providing self. Do not give a lambda!


        Give eventFunction for repeated execution."""

        self.nsmClient = NSMClient(prettyName = name, #will raise an error and exit if this example is not run from NSM.
            saveCallback = self.saveCallbackFunction,
            openOrNewCallback = self.openOrNewCallbackFunction,
            supportsSaveStatus = False,         # Change this to True if your program announces it's save status to NSM
            exitProgramCallback = self.exitCallbackFunction,
            broadcastCallback = self.broadcastCallbackFunction,
            hideGUICallback = None, #replace with your hiding function. You need to answer in your function with nsmClient.announceGuiVisibility(False)
            showGUICallback = None,  #replace with your showing function. You need to answer in your function with nsmClient.announceGuiVisibility(True)
            loggingLevel = "info", #"info" for development or debugging, "error" for production. default is error.
            )

        if eventFunction:
            self.event = eventFunction

        for delay, func in delayedFunctions:
            if type(func) is str:
                func = eval('lambda self=self: ' + func )
            t = Timer(interval=delay, function=func, args=())
            t.start()

        while True:
            self.nsmClient.reactToMessage()
            self.event(self.nsmClient)
            sleep(0.05)

if __name__ == '__main__':
    """This is the most minimal nsm client in existence"""
    BaseClient(name="testclient_base") #this never returns an object.

0707010000002A000081ED0000000000000000000000016258A65C000008A4000000000000000000000000000000000000005800000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/pynsm/minimalClients/broadcast.py#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PyNSMClient -  A New Session Manager Client-Library in one file.

The Non-Session-Manager by Jonathan Moore Liles <male@tuxfamily.org>: http://non.tuxfamily.org/nsm/
New Session Manager by Nils Hilbricht et al  https://new-session-manager.jackaudio.org
With help from code fragments from https://github.com/attwad/python-osc ( DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE v2 )

MIT License

Copyright (c) since 2014: Laborejo Software Suite <info@laborejo.org>, All rights reserved.

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.
"""

#/nsm/server/announce  #A hack to get this into Agordejo launcher discovery


import base
from time import sleep

def hello(nsmClient):
    nsmClient.broadcast("/index/counter", [hello.i])
    hello.i += 1
    sleep(1)
hello.i = 0


class BroadcastClient(base.BaseClient):

    def broadcastCallbackFunction(self, ourPath, sessionName, ourClientNameUnderNSM, messagePath, listOfArguments):
        print (f"We are only an example client, but surely it would be valuable to react to {messagePath} for {listOfArguments}")

if __name__ == '__main__':
    BroadcastClient(name="testclient_broadcast", eventFunction=hello) #this never returns an object.
0707010000002B000081ED0000000000000000000000016258A65C00000769000000000000000000000000000000000000005400000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/pynsm/minimalClients/label.py#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PyNSMClient -  A New Session Manager Client-Library in one file.

The Non-Session-Manager by Jonathan Moore Liles <male@tuxfamily.org>: http://non.tuxfamily.org/nsm/
New Session Manager by Nils Hilbricht et al  https://new-session-manager.jackaudio.org
With help from code fragments from https://github.com/attwad/python-osc ( DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE v2 )

MIT License

Copyright (c) since 2014: Laborejo Software Suite <info@laborejo.org>, All rights reserved.

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.
"""


#/nsm/server/announce  #A hack to get this into Agordejo launcher discovery

import base

if __name__ == '__main__':
    funcs = [
        (1, 'self.nsmClient.changeLabel("Pretty Name")'),
        #(4, lambda: print ("four")),
    ]
    base.BaseClient(name="testclient_label", delayedFunctions=funcs) #this never returns an object.
0707010000002C0000A1FF00000000000000000000000162D27ACA0000000F000000000000000000000000000000000000005800000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/pynsm/minimalClients/nsmclient.py../nsmclient.py0707010000002D000081A40000000000000000000000016258A65C000000F1000000000000000000000000000000000000006400000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/pynsm/minimalClients/source_me_with_port.bash#!/bin/bash

if [[ -z "$1" ]]; then
    echo "Give NSM OSC Port as only parameter. Afterwards you can run the executable pythons in this dir, directly, without ./"
    exit 1
fi

export NSM_URL=osc.udp://0.0.0.0:$1/
export PATH=$(pwd):$PATH
0707010000002E000081A40000000000000000000000016258A65C00008589000000000000000000000000000000000000004900000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/pynsm/nsmclient.py#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PyNSMClient -  A New Session Manager Client-Library in one file.

The Non-Session-Manager by Jonathan Moore Liles <male@tuxfamily.org>: http://non.tuxfamily.org/nsm/
New Session Manager by Nils Hilbricht et al  https://new-session-manager.jackaudio.org
With help from code fragments from https://github.com/attwad/python-osc ( DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE v2 )

MIT License

Copyright (c) since 2014: Laborejo Software Suite <info@laborejo.org>, All rights reserved.

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.
"""

import logging;
logger: logging.Logger  #filled by init with client logger.

import struct
import socket
from os import getenv, getpid, kill
import os
import os.path
import shutil
from uuid import uuid4
from sys import argv
from signal import signal, SIGTERM, SIGINT, SIGKILL #react to exit signals to close the client gracefully. Or kill if the client fails to do so.
from urllib.parse import urlparse

class _IncomingMessage(object):
    """Representation of a parsed datagram representing an OSC message.

    An OSC message consists of an OSC Address Pattern followed by an OSC
    Type Tag String followed by zero or more OSC Arguments.
    """

    def __init__(self, dgram):
        #NSM Broadcasts are bundles, but very simple ones. We only need to care about the single message it contains.
        #Therefore we can strip the bundle prefix and handle it as normal message.
        if b"#bundle" in dgram:
            bundlePrefix, singleMessage = dgram.split(b"/", maxsplit=1)
            dgram = b"/" + singleMessage  # / eaten by split
            self.isBroadcast = True
        else:
            self.isBroadcast = False
        self.LENGTH = 4 #32 bit
        self._dgram = dgram
        self._parameters = []
        self.parse_datagram()


    def get_int(self, dgram, start_index):
        """Get a 32-bit big-endian two's complement integer from the datagram.

        Args:
        dgram: A datagram packet.
        start_index: An index where the integer starts in the datagram.

        Returns:
        A tuple containing the integer and the new end index.

        Raises:
        ValueError if the datagram could not be parsed.
        """
        try:
            if len(dgram[start_index:]) < self.LENGTH:
                raise ValueError('Datagram is too short')
            return (
                struct.unpack('>i', dgram[start_index:start_index + self.LENGTH])[0], start_index + self.LENGTH)
        except (struct.error, TypeError) as e:
            raise ValueError('Could not parse datagram %s' % e)

    def get_string(self, dgram, start_index):
        """Get a python string from the datagram, starting at pos start_index.

        We receive always the full string, but handle only the part from the start_index internally.
        In the end return the offset so it can be added to the index for the next parameter.
        Each subsequent call handles less of the same string, starting further to the right.

        According to the specifications, a string is:
        "A sequence of non-null ASCII characters followed by a null,
        followed by 0-3 additional null characters to make the total number
        of bits a multiple of 32".

        Args:
        dgram: A datagram packet.
        start_index: An index where the string starts in the datagram.

        Returns:
        A tuple containing the string and the new end index.

        Raises:
        ValueError if the datagram could not be parsed.
        """
        #First test for empty string, which is nothing, followed by a terminating \x00 padded by three additional \x00.
        if dgram[start_index:].startswith(b"\x00\x00\x00\x00"):
            return "", start_index + 4

        #Otherwise we have a non-empty string that must follow the rules of the docstring.

        offset = 0
        try:
            while dgram[start_index + offset] != 0:
                offset += 1
            if offset == 0:
                raise ValueError('OSC string cannot begin with a null byte: %s' % dgram[start_index:])
            # Align to a byte word.
            if (offset) % self.LENGTH == 0:
                offset += self.LENGTH
            else:
                offset += (-offset % self.LENGTH)
            # Python slices do not raise an IndexError past the last index,
                # do it ourselves.
            if offset > len(dgram[start_index:]):
                raise ValueError('Datagram is too short')
            data_str = dgram[start_index:start_index + offset]
            return data_str.replace(b'\x00', b'').decode('utf-8'), start_index + offset
        except IndexError as ie:
            raise ValueError('Could not parse datagram %s' % ie)
        except TypeError as te:
            raise ValueError('Could not parse datagram %s' % te)

    def get_float(self, dgram, start_index):
        """Get a 32-bit big-endian IEEE 754 floating point number from the datagram.

          Args:
            dgram: A datagram packet.
            start_index: An index where the float starts in the datagram.

          Returns:
            A tuple containing the float and the new end index.

          Raises:
            ValueError if the datagram could not be parsed.
        """
        try:
            return (struct.unpack('>f', dgram[start_index:start_index + self.LENGTH])[0], start_index + self.LENGTH)
        except (struct.error, TypeError) as e:
            raise ValueError('Could not parse datagram %s' % e)

    def parse_datagram(self):
        try:
            self._address_regexp, index = self.get_string(self._dgram, 0)
            if not self._dgram[index:]:
                # No params is legit, just return now.
                return

            # Get the parameters types.
            type_tag, index = self.get_string(self._dgram, index)
            if type_tag.startswith(','):
                type_tag = type_tag[1:]

            # Parse each parameter given its type.
            for param in type_tag:
                if param == "i":  # Integer.
                    val, index = self.get_int(self._dgram, index)
                elif param == "f":  # Float.
                    val, index = self.get_float(self._dgram, index)
                elif param == "s":  # String.
                    val, index = self.get_string(self._dgram, index)
                else:
                    logger.warning("Unhandled parameter type: {0}".format(param))
                    continue
                self._parameters.append(val)
        except ValueError as pe:
            #raise ValueError('Found incorrect datagram, ignoring it', pe)
            # Raising an error is not ignoring it!
            logger.warning("Found incorrect datagram, ignoring it. {}".format(pe))

    @property
    def oscpath(self):
        """Returns the OSC address regular expression."""
        return self._address_regexp

    @staticmethod
    def dgram_is_message(dgram):
        """Returns whether this datagram starts as an OSC message."""
        return dgram.startswith(b'/')

    @property
    def size(self):
        """Returns the length of the datagram for this message."""
        return len(self._dgram)

    @property
    def dgram(self):
        """Returns the datagram from which this message was built."""
        return self._dgram

    @property
    def params(self):
        """Convenience method for list(self) to get the list of parameters."""
        return list(self)

    def __iter__(self):
        """Returns an iterator over the parameters of this message."""
        return iter(self._parameters)

class _OutgoingMessage(object):
    def __init__(self, oscpath):
        self.LENGTH = 4 #32 bit
        self.oscpath = oscpath
        self._args = []

    def write_string(self, val):
        dgram = val.encode('utf-8')
        diff = self.LENGTH - (len(dgram) % self.LENGTH)
        dgram += (b'\x00' * diff)
        return dgram

    def write_int(self, val):
        return struct.pack('>i', val)

    def write_float(self, val):
        return struct.pack('>f', val)

    def add_arg(self, argument):
        t = {str:"s", int:"i", float:"f"}[type(argument)]
        self._args.append((t, argument))

    def build(self):
        dgram = b''

        #OSC Path
        dgram += self.write_string(self.oscpath)

        if not self._args:
            dgram += self.write_string(',')
            return dgram

        # Write the parameters.
        arg_types = "".join([arg[0] for arg in self._args])
        dgram += self.write_string(',' + arg_types)
        for arg_type, value in self._args:
            f = {"s":self.write_string, "i":self.write_int, "f":self.write_float}[arg_type]
            dgram += f(value)
        return dgram

class NSMNotRunningError(Exception):
    """Error raised when environment variable $NSM_URL was not found."""

class NSMClient(object):
    """The representation of the host programs as NSM sees it.
    Technically consists of an udp server and a udp client.

    Does not run an event loop itself and depends on the host loop.
    E.g. a Qt timer or just a simple while True: sleep(0.1) in Python."""
    def __init__(self, prettyName, supportsSaveStatus, saveCallback, openOrNewCallback, exitProgramCallback, hideGUICallback=None, showGUICallback=None, broadcastCallback=None, sessionIsLoadedCallback=None, loggingLevel = "info"):

        self.nsmOSCUrl = self.getNsmOSCUrl() #this fails and raises NSMNotRunningError if NSM is not available. Host programs can ignore it or exit their program.

        self.realClient = True
        self.cachedSaveStatus = None #save status checks for this.

        global logger
        logger = logging.getLogger(prettyName)
        logger.info("import")
        if loggingLevel == "info" or loggingLevel == 20:
            logging.basicConfig(level=logging.INFO) #development
            logger.info("Starting PyNSM2 Client with logging level INFO. Switch to 'error' for a release!") #the NSM name is not ready yet so we just use the pretty name
        elif loggingLevel == "error" or loggingLevel == 40:
            logging.basicConfig(level=logging.ERROR) #production
        else:
            logging.warning("Unknown logging level: {}. Choose 'info' or 'error'".format(loggingLevel))
            logging.basicConfig(level=logging.INFO) #development

        #given parameters,
        self.prettyName = prettyName #keep this consistent! Settle for one name.
        self.supportsSaveStatus = supportsSaveStatus
        self.saveCallback = saveCallback
        self.exitProgramCallback = exitProgramCallback
        self.openOrNewCallback = openOrNewCallback #The host needs to: Create a jack client with ourClientNameUnderNSM - Open the saved file and all its resources
        self.broadcastCallback = broadcastCallback
        self.hideGUICallback = hideGUICallback
        self.showGUICallback = showGUICallback
        self.sessionIsLoadedCallback = sessionIsLoadedCallback

        #Reactions get the raw _IncomingMessage OSC object
        #A client can add to reactions.
        self.reactions = {
                          "/nsm/client/save" : self._saveCallback,
                          "/nsm/client/show_optional_gui" : lambda msg: self.showGUICallback(),
                          "/nsm/client/hide_optional_gui" : lambda msg: self.hideGUICallback(),
                          "/nsm/client/session_is_loaded" : self._sessionIsLoadedCallback,
                          #Hello source-code reader. You can add your own reactions here by nsmClient.reactions[oscpath]=func, where func gets the raw _IncomingMessage OSC object as argument.
                          #broadcast is handled directly by the function because it has more parameters
                          }
        #self.discardReactions = set(["/nsm/client/session_is_loaded"])
        self.discardReactions = set()

        #Networking and Init
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #internet, udp
        self.sock.bind(('', 0)) #pick a free port on localhost.
        ip, port = self.sock.getsockname()
        self.ourOscUrl = f"osc.udp://{ip}:{port}/"

        self.executableName = self.getExecutableName()

        #UNIX Signals. Used for quit.
        signal(SIGTERM, self.sigtermHandler) #NSM sends only SIGTERM. #TODO: really? pynsm version 1 handled sigkill as well.
        signal(SIGINT, self.sigtermHandler)

        #The following instance parameters are all set in announceOurselves
        self.serverFeatures = None
        self.sessionName = None
        self.ourPath = None
        self.ourClientNameUnderNSM = None
        self.ourClientId = None # the "file extension" of ourClientNameUnderNSM
        self.isVisible = None #set in announceGuiVisibility
        self.saveStatus = True # true is clean. false means we need saving.

        self.announceOurselves()

        assert self.serverFeatures, self.serverFeatures
        assert self.sessionName, self.sessionName
        assert self.ourPath, self.ourPath
        assert self.ourClientNameUnderNSM, self.ourClientNameUnderNSM

        self.sock.setblocking(False) #We have waited for tha handshake. Now switch blocking off because we expect sock.recvfrom to be empty in 99.99...% of the time so we shouldn't wait for the answer.
        #After this point the host must include self.reactToMessage in its event loop

        #We assume we are save at startup.
        self.announceSaveStatus(isClean = True)

        logger.info("NSMClient client init complete. Going into listening mode.")


    def reactToMessage(self):
        """This is the main loop message. It is added to the clients event loop."""
        try:
            data, addr = self.sock.recvfrom(4096) #4096 is quite big. We don't expect nsm messages this big. Better safe than sorry. However, messages will crash the program if they are bigger than 4096.
        except BlockingIOError: #happens while no data is received. Has nothing to do with blocking or not.
            return None

        msg = _IncomingMessage(data)
        if msg.oscpath in self.reactions:
            self.reactions[msg.oscpath](msg)
        elif msg.oscpath in self.discardReactions:
            pass
        elif msg.oscpath == "/reply" and msg.params == ["/nsm/server/open", "Loaded."]: #NSM sends that all programs of the session were loaded.
            logger.info ("Got /reply Loaded from NSM Server")
        elif msg.oscpath == "/reply" and msg.params == ["/nsm/server/save", "Saved."]: #NSM sends that all program-states are saved. Does only happen from the general save instruction, not when saving our client individually
            logger.info ("Got /reply Saved from NSM Server")
        elif msg.isBroadcast:
            if self.broadcastCallback:
                logger.info (f"Got broadcast with messagePath {msg.oscpath} and listOfArguments {msg.params}")
                self.broadcastCallback(self.ourPath, self.sessionName, self.ourClientNameUnderNSM, msg.oscpath, msg.params)
            else:
                logger.info (f"No callback for broadcast! Got messagePath {msg.oscpath} and listOfArguments {msg.params}")
        elif msg.oscpath == "/error":
            logger.warning("Got /error from NSM Server. Path: {} , Parameter: {}".format(msg.oscpath, msg.params))
        else:
            logger.warning("Reaction not implemented:. Path: {} , Parameter: {}".format(msg.oscpath, msg.params))


    def send(self, path:str, listOfParameters:list, host=None, port=None):
        """Send any osc message. Defaults to nsmd URL.
        Will not wait for an answer but return None."""
        if host and port:
            url = (host, port)
        else:
            url = self.nsmOSCUrl
        msg = _OutgoingMessage(path)
        for arg in listOfParameters:
            msg.add_arg(arg) #type is auto-determined by outgoing message
        self.sock.sendto(msg.build(), url)

    def getNsmOSCUrl(self):
        """Return and save the nsm osc url or raise an error"""
        nsmOSCUrl = getenv("NSM_URL")
        if not nsmOSCUrl:
            raise NSMNotRunningError("New-Session-Manager environment variable $NSM_URL not found.")
        else:
            #osc.udp://hostname:portnumber/
            o = urlparse(nsmOSCUrl)
            #return o.hostname, o.port #this always make the hostname lowercase. usually it does not matter, but we got crash reports. Alternative:
            return o.netloc.split(":")[0], o.port

    def getExecutableName(self):
        """Finding the actual executable name can be a bit hard
        in Python. NSM wants the real starting point, even if
        it was a bash script.
        """
        #TODO: I really don't know how to find out the name of the bash script
        fullPath = argv[0]
        assert os.path.dirname(fullPath) in os.environ["PATH"], (fullPath, os.path.dirname(fullPath), os.environ["PATH"]) #NSM requires the executable to be in the path. No excuses. This will never happen since the reference NSM server-GUI already checks for this.

        executableName = os.path.basename(fullPath)
        assert not "/" in executableName, executableName #see above.
        return executableName

    def announceOurselves(self):
        """Say hello to NSM and tell it we are ready to receive
        instructions

        /nsm/server/announce s:application_name s:capabilities s:executable_name i:api_version_major i:api_version_minor i:pid"""

        def buildClientFeaturesString():
            #:dirty:switch:progress:
            result = []
            if self.supportsSaveStatus:
                result.append("dirty")
            if self.hideGUICallback and self.showGUICallback:
                result.append("optional-gui")
            if result:
                return ":".join([""] + result + [""])
            else:
                return ""

        logger.info("Sending our NSM-announce message")

        announce = _OutgoingMessage("/nsm/server/announce")
        announce.add_arg(self.prettyName)  #s:application_name
        announce.add_arg(buildClientFeaturesString()) #s:capabilities
        announce.add_arg(self.executableName)  #s:executable_name
        announce.add_arg(1)  #i:api_version_major
        announce.add_arg(2)  #i:api_version_minor
        announce.add_arg(int(getpid())) #i:pid
        hostname, port = self.nsmOSCUrl
        assert hostname, self.nsmOSCUrl
        assert port, self.nsmOSCUrl
        self.sock.sendto(announce.build(), self.nsmOSCUrl)

        #Wait for /reply (aka 'Howdy, what took you so long?)
        data, addr = self.sock.recvfrom(1024)
        msg = _IncomingMessage(data)

        if msg.oscpath == "/error":
            originalMessage, errorCode, reason = msg.params
            logger.error("Code {}: {}".format(errorCode, reason))
            quit()

        elif msg.oscpath == "/reply":
            nsmAnnouncePath, welcomeMessage, managerName, self.serverFeatures = msg.params
            assert nsmAnnouncePath == "/nsm/server/announce", nsmAnnouncePath
            logger.info("Got /reply " + welcomeMessage)

            #Wait for /nsm/client/open
            data, addr = self.sock.recvfrom(1024)
            msg = _IncomingMessage(data)
            assert msg.oscpath == "/nsm/client/open", msg.oscpath
            self.ourPath, self.sessionName, self.ourClientNameUnderNSM = msg.params
            self.ourClientId = os.path.splitext(self.ourClientNameUnderNSM)[1][1:]
            logger.info("Got '/nsm/client/open' from NSM. Telling our client to load or create a file with name {}".format(self.ourPath))
            self.openOrNewCallback(self.ourPath, self.sessionName, self.ourClientNameUnderNSM) #Host function to either load an existing session or create a new one.
            logger.info("Our client should be done loading or creating the file {}".format(self.ourPath))
            replyToOpen = _OutgoingMessage("/reply")
            replyToOpen.add_arg("/nsm/client/open")
            replyToOpen.add_arg("{} is opened or created".format(self.prettyName))
            self.sock.sendto(replyToOpen.build(), self.nsmOSCUrl)
        else:
            raise ValueError("Unexpected message path after announce: {}".format((msg.oscpath, msg.params)))

    def announceGuiVisibility(self, isVisible):
        message = "/nsm/client/gui_is_shown" if isVisible else "/nsm/client/gui_is_hidden"
        self.isVisible = isVisible
        guiVisibility = _OutgoingMessage(message)
        logger.info("Telling NSM that our clients switched GUI visibility to: {}".format(message))
        self.sock.sendto(guiVisibility.build(), self.nsmOSCUrl)

    def announceSaveStatus(self, isClean):
        """Only send to the NSM Server if there was really a change"""
        if not self.supportsSaveStatus:
            return

        if not isClean == self.cachedSaveStatus:
            message = "/nsm/client/is_clean" if isClean else "/nsm/client/is_dirty"
            self.cachedSaveStatus = isClean
            saveStatus = _OutgoingMessage(message)
            logger.info("Telling NSM that our clients save state is now: {}".format(message))
            self.sock.sendto(saveStatus.build(), self.nsmOSCUrl)

    def _saveCallback(self, msg):
        logger.info("Telling our client to save as {}".format(self.ourPath))
        self.saveCallback(self.ourPath, self.sessionName, self.ourClientNameUnderNSM)
        replyToSave = _OutgoingMessage("/reply")
        replyToSave.add_arg("/nsm/client/save")
        replyToSave.add_arg("{} saved".format(self.prettyName))
        self.sock.sendto(replyToSave.build(), self.nsmOSCUrl)
        #it is assumed that after saving the state is clear
        self.announceSaveStatus(isClean = True)


    def _sessionIsLoadedCallback(self, msg):
        if self.sessionIsLoadedCallback:
            logger.info("Received 'Session is Loaded'. Our client supports it. Forwarding message...")
            self.sessionIsLoadedCallback()
        else:
            logger.info("Received 'Session is Loaded'. Our client does not support it, which is the default. Discarding message...")

    def sigtermHandler(self, signal, frame):
        """Wait for the user to quit the program

        The user function does not need to exit itself.
        Just shutdown audio engines etc.

        It is possible, that the client does not implement quit
        properly. In that case NSM protocol demands that we quit anyway.
        No excuses.

        Achtung GDB! If you run your program with
            gdb --args python foo.py
        the Python signal handler will not work. This has nothing to do with this library.
        """
        logger.info("Telling our client to quit.")
        self.exitProgramCallback(self.ourPath, self.sessionName, self.ourClientNameUnderNSM)
        #There is a chance that exitProgramCallback will hang and the program won't quit. However, this is broken design and bad programming. We COULD place a timeout here and just kill after 10s or so, but that would make quitting our responsibility and fixing a broken thing.
        #If we reach this point we have reached the point of no return. Say goodbye.
        logger.warning("Client did not quit on its own. Sending SIGKILL.")
        kill(getpid(), SIGKILL)
        logger.error("SIGKILL did nothing. Do it manually.")

    def debugResetDataAndExit(self):
        """This is solely meant for debugging and testing. The user way of action should be to
        remove the client from the session and add a new instance, which will get a different
        NSM-ID.
        Afterwards we perform a clean exit."""
        logger.warning("debugResetDataAndExit will now delete {} and then request an exit.".format(self.ourPath))
        if os.path.exists(self.ourPath):
            if os.path.isfile(self.ourPath):
                try:
                    os.remove(self.ourPath)
                except Exception as e:
                    logger.info(e)
            elif os.path.isdir(self.ourPath):
                try:
                    shutil.rmtree(self.ourPath)
                except Exception as e:
                    logger.info(e)
        else:
            logger.info("{} does not exist.".format(self.ourPath))
        self.serverSendExitToSelf()

    def serverSendExitToSelf(self):
        """If you want a very strict client you can block any non-NSM quit-attempts, like ignoring a
        qt closeEvent, and instead send the NSM Server a request to close this client.
        This method is a shortcut to do just that.
        """
        logger.info("Sending SIGTERM to ourselves to trigger the exit callback.")
        #if "server-control" in self.serverFeatures:
        #    message = _OutgoingMessage("/nsm/server/stop")
        #    message.add_arg("{}".format(self.ourClientId))
        #    self.sock.sendto(message.build(), self.nsmOSCUrl)
        #else:
        kill(getpid(), SIGTERM) #this calls the exit callback

    def serverSendSaveToSelf(self):
        """Some clients want to offer a manual Save function, mostly for psychological reasons.
        We offer a clean solution in calling this function which will trigger a round trip over the
        NSM server so our client thinks it received a Save instruction. This leads to a clean
        state with a good saveStatus and no required extra functionality in the client."""

        logger.info("instructing the NSM-Server to send Save to ourselves.")
        if "server-control" in self.serverFeatures:
            #message = _OutgoingMessage("/nsm/server/save") # "Save All" Command.
            message = _OutgoingMessage("/nsm/gui/client/save")
            message.add_arg("{}".format(self.ourClientId))
            self.sock.sendto(message.build(), self.nsmOSCUrl)
        else:
            logger.warning("...but the NSM-Server does not support server control. Server only supports: {}".format(self.serverFeatures))

    def changeLabel(self, label:str):
        """This function is implemented because it is provided by NSM. However, it does not much.
        The message gets received but is not saved.
        The official NSM GUI uses it but then does not save it.
        We would have to send it every startup ourselves.

        This is fine for us as clients, but you need to provide a GUI field to enter that label."""
        logger.info("Telling the NSM-Server that our label is now " + label)
        message = _OutgoingMessage("/nsm/client/label")
        message.add_arg(label)  #s:label
        self.sock.sendto(message.build(), self.nsmOSCUrl)

    def broadcast(self, path:str, arguments:list):
        """/nsm/server/broadcast s:path [arguments...]
        We, as sender, will not receive the broadcast back.

        Broadcasts starting with /nsm are not allowed and will get discarded by the server
        """
        if path.startswith("/nsm"):
            logger.warning("Attempted broadbast starting with /nsm. Not allwoed")
        else:
            logger.info("Sending broadcast " + path + repr(arguments))
            message = _OutgoingMessage("/nsm/server/broadcast")
            message.add_arg(path)
            for arg in arguments:
                message.add_arg(arg)  #type autodetect
            self.sock.sendto(message.build(), self.nsmOSCUrl)

    def importResource(self, filePath):
        """aka. import into session

        ATTENTION! You will still receive an absolute path from this function. You need to make
        sure yourself that this path will not be saved in your save file, but rather use a place-
        holder that gets replaced by the actual session path each time. A good point is after
        serialisation. search&replace for the session prefix ("ourPath") and replace it with a tag
        e.g. <sessionDirectory>. The opposite during load.
        Only such a behaviour will make your session portable.

        Do not use the following pattern: An alternative that comes to mind is to only work with
        relative paths and force your programs workdir to the session directory. Better work with
        absolute paths internally .

        Symlinks given path into session dir and returns the linked path relative to the ourPath.
        It can handles single files as well as whole directories.

        if filePath is already a symlink we do not follow it. os.path.realpath or os.readlink will
        not be used.

        Multilayer links may indicate a users ordering system that depends on
        abstractions. e.g. with mounted drives under different names which get symlinked to a
        reliable path.

        Basically do not question the type of our input filePath.

        tar with the follow symlink option has os.path.realpath behaviour and therefore is able
        to follow multiple levels of links anyway.

        A hardlink does not count as a link and will be detected and treated as real file.

        Cleaning up a session directory is either responsibility of the user
        or of our client program. We do not provide any means to unlink or delete files from the
        session directory.
        """

        #Even if the project was not saved yet now it is time to make our directory in the NSM dir.
        if not os.path.exists(self.ourPath):
            os.makedirs(self.ourPath)

        filePath = os.path.abspath(filePath) #includes normalisation
        if not os.path.exists(self.ourPath):raise FileNotFoundError(self.ourPath)
        if not os.path.isdir(self.ourPath): raise NotADirectoryError(self.ourPath)
        if not os.access(self.ourPath, os.W_OK): raise PermissionError("not writable", self.ourPath)

        if not os.path.exists(filePath):raise FileNotFoundError(filePath)
        if os.path.isdir(filePath): raise IsADirectoryError(filePath)
        if not os.access(filePath, os.R_OK): raise PermissionError("not readable", filePath)

        filePathInOurSession = os.path.commonprefix([filePath, self.ourPath]) == self.ourPath
        linkedPath = os.path.join(self.ourPath, os.path.basename(filePath))
        linkedPathAlreadyExists = os.path.exists(linkedPath)

        if not os.access(os.path.dirname(linkedPath), os.W_OK): raise PermissionError("not writable", os.path.dirname(linkedPath))


        if filePathInOurSession:
            #loadResource from our session dir. Portable session, manually copied beforehand or just loading a link again.
            linkedPath = filePath #we could return here, but we continue to get the tests below.
            logger.info(f"tried to import external resource {filePath} but this is already in our session directory. We use this file directly instead. ")

        elif linkedPathAlreadyExists and os.readlink(linkedPath) == filePath:
            #the imported file already exists as link in our session dir. We do not link it again but simply report the existing link.
            #We only check for the first target of the existing link and do not follow it through to a real file.
            #This way all user abstractions and file structures will be honored.
            linkedPath = linkedPath
            logger.info(f"tried to import external resource {filePath} but this was already linked to our session directory before. We use the old link: {linkedPath} ")

        elif linkedPathAlreadyExists:
            #A new file shall be imported but it would create a linked name which already exists in our session dir.
            #Because we already checked for a new link to the same file above this means actually linking a different file so we need to differentiate with a unique name
            firstpart, extension = os.path.splitext(linkedPath)
            uniqueLinkedPath = firstpart + "." + uuid4().hex + extension
            assert not os.path.exists(uniqueLinkedPath)
            os.symlink(filePath, uniqueLinkedPath)
            logger.info(self.ourClientNameUnderNSM + f":pysm2: tried to import external resource {filePath} but potential target link {linkedPath} already exists. Linked to {uniqueLinkedPath} instead.")
            linkedPath = uniqueLinkedPath

        else: #this is the "normal" case. External resources will be linked.
            assert not os.path.exists(linkedPath)
            os.symlink(filePath, linkedPath)
            logger.info(f"imported external resource {filePath} as link {linkedPath}")

        assert os.path.exists(linkedPath), linkedPath
        return linkedPath

class NullClient(object):
    """Use this as a drop-in replacement if your program has a mode without NSM but you don't want
    to change the code itself.
    This was originally written for programs that have a core-engine and normal mode of operations
    is a GUI with NSM but they also support commandline-scripts and batch processing.
    For these you don't want NSM."""

    def __init__(self, *args, **kwargs):
        self.realClient = False
        self.ourClientNameUnderNSM = "NSM Null Client"

    def announceSaveStatus(self, *args):
        pass

    def announceGuiVisibility(self, *args):
        pass

    def reactToMessage(self):
        pass

    def importResource(self):
        return ""

    def serverSendExitToSelf(self):
        quit()
0707010000002F000081ED0000000000000000000000016258A65C000011A0000000000000000000000000000000000000005300000000new-session-manager-1.6.0+git.20220415.0f6719c/extras/pynsm/test-importResource.py#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PyNSMClient -  A New Session Manager Client-Library in one file.

The Non-Session-Manager by Jonathan Moore Liles <male@tuxfamily.org>: http://non.tuxfamily.org/nsm/
New Session Manager by Nils Hilbricht et al  https://new-session-manager.jackaudio.org
With help from code fragments from https://github.com/attwad/python-osc ( DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE v2 )

MIT License

Copyright (c) since 2014: Laborejo Software Suite <info@laborejo.org>, All rights reserved.

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.
"""

#Test file for the resource import function in nsmclient.py


if __name__ == "__main__":

    from nsmclient import NSMClient
    #We do not start nsmclient, just the the function with temporary files
    importResource = NSMClient.importResource
    import os, os.path

    import logging
    logging.basicConfig(level=logging.INFO)

    from inspect import currentframe
    def get_linenumber():
        cf = currentframe()
        return cf.f_back.f_lineno

    from tempfile import mkdtemp
    class self(object):
        ourPath = mkdtemp()
        ourClientNameUnderNSM = "Loader Test"
    assert os.path.isdir(self.ourPath)


    #First a meta test to see if our system is working:
    assert os.path.exists("/etc/hostname")
    try:
        result = importResource(self, "/etc/hostname") #should not fail!
    except FileNotFoundError:
        pass
    else:
        print (f"Meta Test System works as of line {get_linenumber()}")
        print ("""You should not see any "Test Error" messages""")
        print ("Working in", self.ourPath)
        print (f"Removing {result} for a clean test environment")
        os.remove(result)
        print()

    #Real tests

    try:
        importResource(self, "/floot/nonexistent") #should fail
    except FileNotFoundError:
        pass
    else:
        print (f"Test Error in line {get_linenumber()}")

    try:
        importResource(self, "////floot//nonexistent/") #should fail
    except FileNotFoundError:
        pass
    else:
        print (f"Test Error in line {get_linenumber()}")

    try:
        importResource(self, "/etc/shadow") #reading not possible
    except PermissionError:
        pass
    else:
        print (f"Test Error in line {get_linenumber()}")


    assert os.path.exists("/etc/hostname")
    try:
        org = self.ourPath
        self.ourPath = "/" #writing not possible
        importResource(self, "/etc/hostname")
    except PermissionError:
        self.ourPath = org
    else:
        print (f"Test Error in line {get_linenumber()}")


    from tempfile import NamedTemporaryFile
    tmpf = NamedTemporaryFile()
    assert os.path.exists("/etc/hostname")
    try:
        org = self.ourPath
        self.ourPath = tmpf.name #writable, but not a dir
        importResource(self, "/etc/hostname")
    except NotADirectoryError:
        self.ourPath = org
    else:
        print (f"Test Error in line {get_linenumber()}")

    #Test the real purpose
    result = importResource(self, "/etc/hostname")
    print ("imported to", result)


    #Test what happens if we try to import already imported resource again
    result = importResource(self, result)
    print ("imported to", result)

    #Test what happens if we try to import a resource that would result in a name collision
    result = importResource(self, "/etc/hostname")
    print ("imported to", result)

    #Count the number of resulting files.
    assert len(os.listdir(self.ourPath)) == 2

07070100000030000081A40000000000000000000000016258A65C000012E1000000000000000000000000000000000000003B00000000new-session-manager-1.6.0+git.20220415.0f6719c/meson.build##############################################################################
# Copyright (C) 2020- Nils Hilbricht
#
# This file is part of New-Session-Manager
#
# New-Session-Manager 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.
#
# New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.
##############################################################################


#Please keep the version number in a separate line so we can automatically update it.
#Also keep it at the beginning of the line and leave spaces around the colon.
#It's source is in src/nsmd.cpp #DEFINE VERSION_STRING
project(
'new-session-manager',
'c', 'cpp',
version : '1.6.0',
license : 'GPLv3',
)

##############
#Dependencies
##############

liblodep = dependency('liblo') #and not 'lo'
threaddep = dependency('threads')
jackdep = dependency('jack', required: get_option('jackpatch')) #and not 'libjack'

cc = meson.get_compiler('c')
fltkdep = cc.find_library('fltk', required: get_option('nsm-legacy-gui') or get_option('nsm-proxy'))
fltkimagesdep = cc.find_library('fltk_images', required: get_option('nsm-legacy-gui'))
fluid = find_program('fluid', required: get_option('nsm-proxy'))


##############
#Build Targets
##############

executable('nsmd',
    sources: ['src/nsmd.cpp', 'src/debug.cpp', 'src/Endpoint.cpp', 'src/file.cpp', 'src/Thread.cpp'],
    dependencies: [liblodep, threaddep],
    install: true,
    )

install_man(['docs/src/nsmd.1'])
install_data('docs/index.html', install_dir : get_option('datadir') / 'doc/new-session-manager')
install_data('docs/api/index.html', install_dir : get_option('datadir') / 'doc/new-session-manager/api')
install_data('CHANGELOG', install_dir : get_option('datadir') / 'doc/new-session-manager')
install_data('README.md', install_dir : get_option('datadir') / 'doc/new-session-manager')

#For options see meson_options.txt
#All get_options are default=true

if get_option('jackpatch')

    executable('jackpatch',
        'src/jackpatch.c',
        dependencies: [liblodep, jackdep],
        install: true,
        )

    install_data('src/jackpatch.svg', install_dir : get_option('datadir') / 'icons/hicolor/scalable/apps')
    install_data('src/org.jackaudio.jackpatch.desktop', install_dir : get_option('datadir') / 'applications')
    install_man(['docs/src/jackpatch.1', ])
endif


if get_option('nsm-proxy')

    NSM_Proxy_UI_cpp = custom_target(
        'NSM_Proxy_UI.cpp',
        output : 'NSM_Proxy_UI.C',
        input : 'src/NSM_Proxy_UI.fl',
        command : [fluid, '-c', '-o', '@OUTPUT@', '@INPUT@'],
    )

    NSM_Proxy_UI_h = custom_target(
        'NSM_Proxy_UI.h',
        output : 'NSM_Proxy_UI.H',
        input : 'src/NSM_Proxy_UI.fl',
        command : [fluid, '-c', '-h', '@OUTPUT@', '@INPUT@'],
    )


    executable('nsm-proxy',
        sources: ['src/nsm-proxy.cpp', 'src/debug.cpp'],
        dependencies: [liblodep, threaddep],
        install: true,
    )

    executable('nsm-proxy-gui',
        sources: ['src/nsm-proxy-gui.cpp', [NSM_Proxy_UI_cpp, NSM_Proxy_UI_h]],
        dependencies: [fltkdep, liblodep, threaddep],
        install: true,
    )

    install_data('src/nsm-proxy.svg', install_dir : get_option('datadir') / 'icons/hicolor/scalable/apps')
    install_data('src/org.jackaudio.nsm-proxy.desktop', install_dir : get_option('datadir') / 'applications')
    install_man(['docs/src/nsm-proxy.1', 'docs/src/nsm-proxy-gui.1'])

endif

if get_option('nsm-legacy-gui')

    executable('nsm-legacy-gui',
        sources: ['src/nsm-legacy-gui.cpp', 'src/debug.cpp', 'src/Endpoint.cpp', 'src/Thread.cpp', 'src/FL/Fl_Scalepack.C'],
        dependencies: [fltkimagesdep, fltkdep, liblodep, threaddep],
        install: true,
        )

    install_data('src/org.jackaudio.nsm-legacy-gui.desktop', install_dir : get_option('datadir') / 'applications')
    install_data('src/nsm-legacy-gui.svg', install_dir : get_option('datadir') / 'icons/hicolor/scalable/apps')
    install_man(['docs/src/nsm-legacy-gui.1', 'docs/src/non-session-manager.1'])

    #Symlinking is a one-way operation and can't be uninstalled, we rely on distribution packages for that
    meson.add_install_script('sh', '-c',
        'ln -sf nsm-legacy-gui ${DESTDIR}@0@/@1@/non-session-manager'.format(
            get_option('prefix'), get_option('bindir')))

endif
07070100000031000081A40000000000000000000000016258A65C00000146000000000000000000000000000000000000004100000000new-session-manager-1.6.0+git.20220415.0f6719c/meson_options.txtoption('nsm-legacy-gui', type : 'boolean', value : true, description : 'Build nsm-legacy-gui, symlinked as non-session-manager')
option('jackpatch', type : 'boolean', value : true, description : 'Build jackpatch client')
option('nsm-proxy', type : 'boolean', value : true, description : 'Build nsm-proxy client with GUI.')



07070100000032000041ED0000000000000000000000036258A65C00000000000000000000000000000000000000000000003300000000new-session-manager-1.6.0+git.20220415.0f6719c/src07070100000033000081A40000000000000000000000016258A65C00009A43000000000000000000000000000000000000004000000000new-session-manager-1.6.0+git.20220415.0f6719c/src/Endpoint.cpp
/*******************************************************************************/
/* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager")     */
/* Copyright (C) 2020- Nils Hilbricht                                          */
/*                                                                             */
/* This file is part of New-Session-Manager                                    */
/*                                                                             */
/* New-Session-Manager 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.                                         */
/*                                                                             */
/* New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
/*******************************************************************************/

#include <lo/lo.h>
#include "debug.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "Endpoint.hpp"

#include "Thread.hpp"

#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-result"

namespace OSC
{

    /**********/
    /* Method */
    /**********/

    Method::Method ( )
    {
        _path = _typespec = _documentation = 0;
    }

    Method::~Method ( )
    {
        if ( _path )
            free( _path );
        if ( _typespec )
            free( _typespec );
        if ( _documentation )
            free( _documentation );
    }

    /**********/
    /* Signal */
    /**********/

    Signal::Signal ( const char *path, Direction dir )
    {
        _direction = dir;
        _path = NULL;
        if ( path )
            _path = strdup( path );
        _value = 0.0f;
        _endpoint = NULL;
        _peer = NULL;
        _documentation = 0;
        _user_data = 0;
        _connection_state_callback = 0;
        _connection_state_userdata = 0;
    }

    Signal::~Signal ( )
    {
        if ( _endpoint )
        {
            _endpoint->del_signal( this );
        }

        if ( _path )
            free( _path );
        _path = NULL;

        _endpoint = NULL;
    }

    void
    Signal::rename ( const char *path )
    {
        char *new_path;
        asprintf( &new_path, "%s%s", _endpoint->name() ? _endpoint->name() : "", path );

        MESSAGE( "Renaming signal %s to %s", this->path(), new_path );

        /* if ( _direction == Signal::Input ) */
        /* { */
            lo_server_del_method( _endpoint->_server, _path, NULL );
            lo_server_add_method( _endpoint->_server, new_path, NULL, _endpoint->osc_sig_handler, this );
        /* } */

        for ( std::list<Peer*>::iterator i = _endpoint->_peers.begin();
              i != _endpoint->_peers.end();
              ++i )
        {
            _endpoint->send( (*i)->addr, "/signal/renamed", _path, new_path );
        }

        _endpoint->rename_translation_destination( _path, new_path );

        free( _path );
        _path = new_path;
    }

    void
    Signal::value ( float f )
    {
        if ( f == _value )
            return;

        _value = f;

        if ( direction() == Output )
        {
            for ( std::list<Peer*>::iterator i = _endpoint->_peers.begin();
                  i != _endpoint->_peers.end();
                  ++i )
            {
                _endpoint->send( (*i)->addr,
                                 path(),
                                 f );
            }

            // free(s);
        }
        /* else if ( direction() == Input ) */
        /* { */
        /*     MESSAGE( "Sending value feedback for signal %s...", path() ); */
        /*     for ( std::list<Signal*>::iterator i = _incoming.begin(); */
        /*           i != _incoming.end(); */
        /*           ++i ) */
        /*     { */
        /*         MESSAGE( "Sending value feedback to %s %s %f", lo_address_get_url( (*i)->_peer->addr), (*i)->path() , f); */
        /*         _endpoint->send( (*i)->_peer->addr,  */
        /*                          (*i)->path(), */
        /*                          f ); */
        /*     } */
        /* } */
    }

    /* char * */
    /* Signal::get_output_connection_peer_name_and_path ( int n ) */
    /* { */
    /*     Signal *t = NULL; */

    /*     int j = 0; */
    /*     for ( std::list<Signal*>::const_iterator i = _outgoing.begin(); */
    /*           i != _outgoing.end(); */
    /*           ++i, ++j ) */
    /*     { */
    /*         if ( j == n ) */
    /*         { */
    /*             t = *i; */
    /*             break; */
    /*         } */
    /*     } */

    /*     if ( t ) */
    /*     { */
    /*         char *r; */
    /*         asprintf( &r, "%s%s", t->_peer->name, t->path() ); */

    /*         return r; */
    /*     } */
    /*     else */
    /*         return NULL; */
    /* } */
    /*  */


    void
    Endpoint::error_handler(int num, const char *msg, const char *path)
    {
        WARNING( "LibLO server error %d in path %s: %s\n", num, path, msg);
    }

    Endpoint::Endpoint ( )
    {
        _learning_path = NULL;
        _peer_signal_notification_callback = 0;
        _peer_signal_notification_userdata = 0;
        _peer_scan_complete_callback = 0;
        _peer_scan_complete_userdata = 0;
        _server = 0;
        _name = 0;
        owner = 0;
    }

    int
    Endpoint::init ( int proto, const char *port )
    {
        MESSAGE( "Creating OSC server" );

        _server = lo_server_new_with_proto( port, proto, error_handler );

        char *url = lo_server_get_url( _server );
        _addr = lo_address_new_from_url( url );
        free( url );

        if ( ! _server )
        {
            WARNING( "Error creating OSC server" );
            return -1;
        }

        add_method( "/signal/hello", "ss", &Endpoint::osc_sig_hello, this, "" );
        add_method( "/signal/connect", "ss", &Endpoint::osc_sig_connect, this, "" );
        add_method( "/signal/disconnect", "ss", &Endpoint::osc_sig_disconnect, this, "" );
        add_method( "/signal/renamed", "ss", &Endpoint::osc_sig_renamed, this, "" );
        add_method( "/signal/removed", "s", &Endpoint::osc_sig_removed, this, "" );
        add_method( "/signal/created", "ssfff", &Endpoint::osc_sig_created, this, "" );
        add_method( "/signal/list", NULL, &Endpoint::osc_signal_lister, this, "" );
        add_method( "/reply", NULL, &Endpoint::osc_reply, this, "" );
        add_method( NULL, NULL, &Endpoint::osc_generic, this, "" );

        return 0;
    }


    Endpoint::~Endpoint ( )
    {
//    lo_server_thread_free( _st );

        for ( std::list<Method*>::iterator i = _methods.begin();
              i != _methods.end();
              i++ )
            delete(*i);

        _methods.clear();

        if ( _server )
        {
            lo_server_free( _server );
            _server = 0;
        }

        lo_address_free( _addr );
        _addr = 0;
    }


    OSC::Signal *
    Endpoint::find_target_by_peer_address ( std::list<Signal*> *l, lo_address addr )
    {

        for ( std::list<Signal*>::iterator i = l->begin();
              i != l->end();
              ++i )
        {
            if ( address_matches( addr, (*i)->_peer->addr ) )
            {
                return *i;
            }
        }

        return NULL;
    }


    OSC::Signal *
    Endpoint::find_peer_signal_by_path ( Peer *p, const char *path )
    {
        for ( std::list<Signal*>::iterator i = p->_signals.begin();
              i != p->_signals.end();
              ++i )
        {
            if ( !strcmp( (*i)->path(), path ) )
                return *i;
        }

        return NULL;
    }

    OSC::Signal *
    Endpoint::find_signal_by_path ( const char *path )
    {
        for ( std::list<Signal*>::iterator i = _signals.begin();
              i != _signals.end();
              ++i )
        {
            if ( !strcmp( (*i)->path(), path ) )
                return *i;
        }

        return NULL;
    }

    void
    Endpoint::hello ( const char *url )
    {
        assert( name() );

        lo_address addr = lo_address_new_from_url ( url );

        char *our_url = this->url();
        send( addr, "/signal/hello", name(), our_url );
        free( our_url );

        lo_address_free( addr );
    }

    void
    Endpoint::handle_hello ( const char *peer_name, const char *peer_url )
    {
        MESSAGE( "Got hello from %s", peer_name );

        Peer *p = find_peer_by_name( peer_name );

        if ( ! p )
        {
            scan_peer( peer_name, peer_url );
        }
        else
        {
            /* maybe the peer has a new URL */

            /* update address */
            lo_address addr = lo_address_new_from_url( peer_url );

            if ( address_matches( addr, p->addr ) )
            {
                free( addr );
                return;
            }

            if ( p->addr )
                free( p->addr );

            p->addr = addr;

            /* scan it while we're at it */
            p->_scanning = true;

            MESSAGE( "Scanning peer %s", peer_name );

            send( p->addr, "/signal/list" );
        }

        if ( name() )
        {
            hello( peer_url );
        }
        else
        {
            MESSAGE( "Not sending hello because we don't have a name yet!" );
        }
    }

    int
    Endpoint::osc_sig_hello ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
    {
        Endpoint *ep = (Endpoint*)user_data;

        const char *peer_name = &argv[0]->s;
        const char *peer_url = &argv[1]->s;

        ep->handle_hello( peer_name, peer_url );

        return 0;
    }

    int
    Endpoint::osc_sig_disconnect ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
    {
        const char *their_name = &argv[0]->s;
        const char *our_name = &argv[1]->s;

        Endpoint *ep = (Endpoint*)user_data;

        Signal *s = ep->find_signal_by_path( our_name );

        if ( ! s )
            return 0;

        if ( s->_direction == Signal::Input )
        {
            MESSAGE( "Peer %s has disconnected from signal %s", our_name, their_name );

            ep->del_translation( their_name );

            if ( s->_connection_state_callback )
                s->_connection_state_callback( s, s->_connection_state_userdata );

            return 0;
        }

        return 0;
    }

    int
    Endpoint::osc_sig_connect ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
    {
        const char *src_path = &argv[0]->s;
        const char *dst_path = &argv[1]->s;

        Endpoint *ep = (Endpoint*)user_data;

        Signal *dst_s = ep->find_signal_by_path( dst_path );

        if ( ! dst_s )
        {
            WARNING( "Unknown destination signal in connection attempt: \"%s\"", dst_path );
            return 0;
        }

        if ( dst_s->_endpoint != ep )
        {
            WARNING( "Got connection request for a destination signal we don't own" );
            return 0;
        }

        MESSAGE( "Has requested signal connection %s |> %s", src_path, dst_s->path() );

        ep->add_translation( src_path, dst_s->path() );

        /* if ( dst_s->_connection_state_callback ) */
        /*     dst_s->_connection_state_callback( dst_s, dst_s->_connection_state_userdata ); */

        return 0;
    }

    int
    Endpoint::osc_sig_removed ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
    {
        const char *name = &argv[0]->s;

        Endpoint *ep = (Endpoint*)user_data;

        Peer *p = ep->find_peer_by_address( lo_message_get_source( msg ) );

        if ( ! p )
        {
            WARNING( "Got signal removed notification from unknown peer." );
            return 0;
        }

        Signal *o = ep->find_peer_signal_by_path( p, name );

        if ( ! o )
        {
            WARNING( "Unknown signal: %s", name  );
            return 0;
        }

        MESSAGE( "Signal %s:%s was removed", o->_peer->name, o->path() );


        if ( ep->_peer_signal_notification_callback )
            ep->_peer_signal_notification_callback( o, Signal::Removed, ep->_peer_signal_notification_userdata );

        p->_signals.remove(o);

        delete o;

        return 0;
    }

    int
    Endpoint::osc_sig_created ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
    {
        Endpoint *ep = (Endpoint*)user_data;

        const char *name = &argv[0]->s;
        const char *direction = &argv[1]->s;
        const float min = argv[2]->f;
        const float max = argv[3]->f;
        const float default_value = argv[4]->f;

        Peer *p = ep->find_peer_by_address( lo_message_get_source( msg ) );

        if ( ! p )
        {
            WARNING( "Got signal creation notification from unknown peer" );
            return 0;
        }

        Signal::Direction dir = Signal::Input;

        if ( !strcmp( direction, "in" ) )
            dir = Signal::Input;
        else if ( !strcmp( direction, "out" ) )
            dir = Signal::Output;

        Signal *s = new Signal( name, dir );

        s->_peer = p;
        s->parameter_limits( min, max, default_value );

        p->_signals.push_back( s );

        MESSAGE( "Peer %s has created signal %s (%s %f %f %f)", p->name,
                  name, direction, min, max, default_value );

        if ( ep->_peer_signal_notification_callback )
            ep->_peer_signal_notification_callback( s, Signal::Created, ep->_peer_signal_notification_userdata );

        return 0;
    }

    int
    Endpoint::osc_sig_renamed ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
    {
        MESSAGE( "Got renamed message." );

        const char *old_name = &argv[0]->s;
        const char *new_name = &argv[1]->s;

        Endpoint *ep = (Endpoint*)user_data;

        Peer *p = ep->find_peer_by_address( lo_message_get_source( msg ) );

        if ( ! p )
        {
            WARNING( "Got signal rename notification from unknown peer." );
            return 0;
        }

        Signal *o = ep->find_peer_signal_by_path( p, old_name );

        if ( ! o )
        {
            WARNING( "Unknown signal: %s", old_name );
            return 0;
        }

        MESSAGE( "Signal %s was renamed to %s", o->_path, new_name );

        ep->rename_translation_source( o->_path, new_name );

        free( o->_path );
        o->_path = strdup( new_name );

        return 0;
    }

    int
    Endpoint::osc_sig_handler ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
    {
        Signal *o;
        float f = 0.0;

        if ( ! strcmp( types, "f" ) )
        {
            /* accept a value for signal named in path */
            o = (Signal*)user_data;
            f = argv[0]->f;
        }
        else if ( ! types || 0 == types[0] )
        {
            /* reply with current value */
            o = (Signal*)user_data;
            o->_endpoint->send( lo_message_get_source( msg ), "/reply", path, o->value() );
            return 0;
        }
        else
        {
            return -1;
        }

        o->_value = f;

        if ( o->_handler )
            o->_handler( f, o->_user_data );

        return 1;
    }

    const char**
    Endpoint::get_connections ( const char *path )
    {
        const char **  conn = NULL;

        int j = 0;
        for ( std::map<std::string,TranslationDestination>::iterator i = _translations.begin();
              i != _translations.end();
              i++ )
        {
            if ( !strcmp( i->second.path.c_str(), path ) )
            {
                conn = (const char**)realloc( conn, sizeof( char * ) * (j+2));
                conn[j++] = i->first.c_str();
            }
        }

        if ( conn )
            conn[j] = 0;

        return conn;
    }

    void
    Endpoint::clear_translations ( void )
    {
        _translations.clear();
    }

    void
    Endpoint::add_translation ( const char *a, const char *b )
    {
        _translations[a].path = b;
    }

    void
    Endpoint::del_translation ( const char *a )
    {
        std::map<std::string,TranslationDestination>::iterator i = _translations.find( a );

        if ( i != _translations.end() )
            _translations.erase( i );
    }

    void
    Endpoint::rename_translation_destination ( const char *a, const char *b )
    {

        for ( std::map<std::string,TranslationDestination>::iterator i = _translations.begin();
              i != _translations.end();
              i++ )
        {
            if ( !strcmp( i->second.path.c_str(), a ) )
            {
                i->second.path = b;
            }
        }
    }

    void
    Endpoint::rename_translation_source ( const char *a, const char *b )
    {
        std::map<std::string,TranslationDestination>::iterator i = _translations.find( a );

        if ( i != _translations.end() )
        {
            _translations[b] = _translations[a];

            _translations.erase( i );
        }
    }

    int
    Endpoint::ntranslations ( void )
    {
        return _translations.size();
    }

    bool
    Endpoint::get_translation ( int n, const char **from, const char **to )
    {
        int j = 0;
        for ( std::map<std::string,TranslationDestination>::const_iterator i = _translations.begin();
              i != _translations.end();
              i++, j++)
        {
            if ( j == n )
            {
                *from = i->first.c_str();
                *to = i->second.path.c_str();
                return true;
            }
        }

        return false;
    }

    int
    Endpoint::osc_generic ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
    {
//        OSC_DMSG();
        Endpoint *ep = (Endpoint*)user_data;

        if ( ep->_learning_path )
        {
            ep->add_translation( path, ep->_learning_path );

            MESSAGE( "Learned translation \"%s\" -> \"%s\"", path, ep->_learning_path );

            free(ep->_learning_path);
            ep->_learning_path = NULL;

            return 0;
        }

        {
            std::map<std::string,TranslationDestination>::iterator i = ep->_translations.find( path );

            if ( i != ep->_translations.end() )
            {
                const char *dpath = i->second.path.c_str();

//                MESSAGE( "Translating message \"%s\" to \"%s\"", path, dpath );

                if ( !strcmp(types, "f" ))
                {
//                    MESSAGE( "recording value %f", argv[0]->f );
                    i->second.current_value = argv[0]->f;
                }

                i->second.suppress_feedback = true;

                lo_send_message(ep->_addr, dpath, msg );
                return 0;
            }
        }

        if ( argc || path[ strlen(path) - 1 ] != '/' )
            return -1;

        for ( std::list<Method*>::const_iterator i = ep->_methods.begin(); i != ep->_methods.end(); ++i )
        {
            if ( ! (*i)->path() )
                continue;

            if (! strncmp( (*i)->path(), path, strlen(path) ) )
            {
                /* asprintf( &stored_path, "%s (%s); %s", path, typespec, argument_description ); */

                ((Endpoint*)user_data)->send( lo_message_get_source( msg ), "/reply", path, (*i)->path() );
            }
        }

        ((Endpoint*)user_data)->send( lo_message_get_source( msg ), "/reply", path );

        return 0;
    }

    int
    Endpoint::osc_signal_lister ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
    {
//        OSC_DMSG();

        MESSAGE( "Listing signals." );

        const char *prefix = NULL;

        if ( argc )
            prefix = &argv[0]->s;

        Endpoint *ep = (Endpoint*)user_data;

        for ( std::list<Signal*>::const_iterator i = ep->_signals.begin(); i != ep->_signals.end(); ++i )
        {
            Signal *o = *i;

            if ( ! prefix || ! strncmp( o->path(), prefix, strlen(prefix) ) )
            {
                ep->send( lo_message_get_source( msg ),
                          "/reply",
                          path,
                          o->path(),
                          o->_direction == Signal::Input ? "in" : "out",
                          o->parameter_limits().min,
                          o->parameter_limits().max,
                          o->parameter_limits().default_value
                    );
            }
        }

        ep->send( lo_message_get_source( msg ), "/reply", path );

        return 0;
    }

    bool
    Endpoint::address_matches ( lo_address addr1, lo_address addr2 )
    {
        char *purl = strdup( lo_address_get_port( addr1 ) );
        char *url = strdup( lo_address_get_port( addr2 ) );

        bool r = !strcmp( purl, url );

        free( purl );
        free( url );

        return r;
    }


    void
    Endpoint::list_peer_signals ( void *v )
    {
        for ( std::list<Peer*>::iterator i = _peers.begin();
              i != _peers.end();
              ++i )
        {
            for ( std::list<Signal*>::iterator j = (*i)->_signals.begin();
                  j != (*i)->_signals.end();
                  ++j )
            {
                if ( _peer_signal_notification_callback )
                    _peer_signal_notification_callback( *j, OSC::Signal::Created, v );
            }
        }
    }

    Peer *
    Endpoint::find_peer_by_address ( lo_address addr )
    {
        char *url = strdup( lo_address_get_port( addr ) );

        Peer *p = NULL;

        for ( std::list<Peer*>::iterator i = _peers.begin();
              i != _peers.end();
              ++i )
        {
            char *purl = strdup( lo_address_get_port( (*i)->addr ) );

            if ( !strcmp( purl, url ) )
            {
                free( purl );
                p = *i;
                break;
            }
            free(purl);
        }

        free( url );

        return p;
    }

    Peer *
    Endpoint::find_peer_by_name ( const char *name )
    {
        for ( std::list<Peer*>::iterator i = _peers.begin();
              i != _peers.end();
              ++i )
        {
            if ( !strcmp( name, (*i)->name ) )
            {
                return *i;
            }
        }

        return NULL;
    }

    bool
    Endpoint::disconnect_signal ( OSC::Signal *s, const char *signal_path )
    {
        if ( s->_direction == Signal::Output )
        {
            for ( std::list<Peer*>::iterator i = _peers.begin();
                  i != _peers.end();
                  ++i )
            {
                send( (*i)->addr, "/signal/disconnect",
                      s->path(),
                      signal_path);
            }

            return true;
        }

        return false;
    }

    bool
    Endpoint::connect_signal( OSC::Signal *s, const char *signal_path )
    {
        if ( s->_direction == Signal::Output )
        {
            for ( std::list<Peer*>::iterator i = _peers.begin();
                  i != _peers.end();
                  i++ )
            {

                send( (*i)->addr, "/signal/connect",
                      s->path(),
                      signal_path );
            }
        }

        return true;
    }

    int
    Endpoint::osc_reply ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )

    {
        Endpoint *ep = (Endpoint*)user_data;

        if ( argc && !strcmp( &argv[0]->s, "/signal/list" ) )
        {
            Peer *p = ep->find_peer_by_address( lo_message_get_source( msg ) );

            if ( ! p )
            {
                WARNING( "Got input list reply from unknown peer." );
                return 0;
            }

            if ( argc == 1 )
            {
                p->_scanning = false;
                MESSAGE( "Done scanning %s", p->name );

                if ( ep->_peer_scan_complete_callback )
                    ep->_peer_scan_complete_callback(ep->_peer_scan_complete_userdata);
            }
            else if ( argc == 6 && p->_scanning )
            {
                Signal *s = ep->find_peer_signal_by_path( p, &argv[1]->s );

                if ( s )
                    return 0;

                MESSAGE( "Peer %s has signal %s (%s)", p->name, &argv[1]->s, &argv[2]->s );

                int dir = 0;

                if ( !strcmp( &argv[2]->s, "in" ) )
                    dir = Signal::Input;
                else if ( !strcmp( &argv[2]->s, "out" ) )
                    dir = Signal::Output;


                s = new Signal( &argv[1]->s, (Signal::Direction)dir );

                s->_peer = p;

                s->parameter_limits( argv[3]->f, argv[4]->f, argv[5]->f );

                p->_signals.push_back( s );

                //         ep->_signals.push_back(s);

                if ( ep->_peer_signal_notification_callback )
                    ep->_peer_signal_notification_callback( s, Signal::Created, ep->_peer_signal_notification_userdata );
            }

            return 0;
        }
        else
            return -1;
    }

    Method *
    Endpoint::add_method ( const char *path, const char *typespec, lo_method_handler handler, void *user_data, const char *argument_description )
    {
//  MESSAGE( "Added OSC method %s (%s)", path, typespec );

        lo_server_add_method( _server, path, typespec, handler, user_data );

        Method *md = new Method;

        if ( path )
            md->_path = strdup( path );
        if ( typespec )
            md->_typespec = strdup( typespec );
        if ( argument_description )
            md->_documentation = strdup( argument_description );

        _methods.push_back( md );

        return md;
    }

    Signal *
    Endpoint::add_signal ( const char *path, Signal::Direction dir, float min, float max, float default_value, signal_handler handler, void *user_data )
    {
        char *s;
        asprintf( &s, "%s%s", name() ? name() : "", path );

        Signal *o = new Signal( s, dir );

        free(s);

        o->_handler = handler;
        o->_user_data = user_data;
        o->_endpoint = this;

        o->parameter_limits( min, max, default_value );

        _signals.push_back( o );

        /* if ( dir == Signal::Input ) */
        /* { */
            lo_server_add_method( _server, o->_path, NULL, osc_sig_handler, o );
        /* } */

        /* tell our peers about it */
        for ( std::list<Peer*>::iterator i = _peers.begin();
              i != _peers.end();
              ++i )
        {
            send( (*i)->addr,
                  "/signal/created",
                  o->path(),
                  o->_direction == Signal::Input ? "in" : "out",
                  min,
                  max,
                  default_value
                );
        }

        return o;
    }

    void
    Endpoint::del_method ( const char *path, const char *typespec )
    {
//  MESSAGE( "Deleted OSC method %s (%s)", path, typespec );

        lo_server_del_method( _server, path, typespec );

        for ( std::list<Method *>::iterator i = _methods.begin(); i != _methods.end(); ++i )
        {
            if ( ! (*i)->path() )
                continue;

            if ( ! strcmp( path, (*i)->path() ) &&
                 ! strcmp( typespec, (*i)->typespec() ) )
            {
                delete *i;
                i = _methods.erase( i );

                break;
            }
        }
    }

    void
    Endpoint::del_method ( Method *meth )
    {
//  MESSAGE( "Deleted OSC method %s (%s)", path, typespec );

        lo_server_del_method( _server, meth->path(), meth->typespec() );

        delete meth;

        _methods.remove( meth );
    }

    void
    Endpoint::del_signal ( Signal *o )
    {
//  MESSAGE( "Deleted OSC method %s (%s)", path, typespec );

        lo_server_del_method( _server, o->path(), NULL );

        /* tell our peers about it */
        for ( std::list<Peer*>::iterator i = _peers.begin();
              i != _peers.end();
              ++i )
        {
            send( (*i)->addr,
                  "/signal/removed",
                  o->path() );
        }

        /* FIXME: clear loopback connections first! */

        _signals.remove( o );
    }

    /* prepare to learn a translation for /path/. The next unhandled message to come through will be mapped to /path/ */
    void
    Endpoint::learn ( const char *path )
    {
        if ( _learning_path )
            free( _learning_path );

        _learning_path = NULL;

        if ( path )
            _learning_path = strdup( path );
    }

    /** if there's a translation with a destination of 'path', then send feedback for it */
    void
    Endpoint::send_feedback ( const char *path, float v )
    {
        for ( std::map<std::string,TranslationDestination>::iterator i = _translations.begin();
              i != _translations.end();
              i++ )
        {
            if ( path && ! strcmp( i->second.path.c_str(), path ) )
            {
                /* found it */
                if ( !i->second.suppress_feedback && i->second.current_value != v )
                {
                    const char *spath = i->first.c_str();

//                    MESSAGE( "Sending feedback to \"%s\": %f", spath, v );

                    /* send to all peers */
                    for ( std::list<Peer*>::iterator p = _peers.begin();
                          p != _peers.end();
                          ++p )
                    {
                        send( (*p)->addr, spath, v );
                    }

                    i->second.current_value = v;
                }

                i->second.suppress_feedback = false;

                /* break; */

            }
        }
    }

    Peer *
    Endpoint::add_peer ( const char *name, const char *url )
    {
        Peer *p = new Peer;

        MESSAGE( "Adding peer %s @ %s...", name, url );

        p->name = strdup( name );
        p->addr = lo_address_new_from_url( url );

        _peers.push_back( p );

        return p;
    }

    void
    Endpoint::scan_peer ( const char *name, const char *url )
    {
        Peer *p = add_peer(name,url);

        p->_scanning = true;

        MESSAGE( "Scanning peer %s", name );

        send( p->addr, "/signal/list" );
    }

    void *
    Endpoint::osc_thread ( void * arg )
    {
        ((Endpoint*)arg)->osc_thread();

        return NULL;
    }

    void
    Endpoint::osc_thread ( void )
    {
        _thread.name( "OSC" );

        MESSAGE( "OSC Thread running" );

        run();
    }

    void
    Endpoint::start ( void )
    {

        if ( !_thread.clone( &Endpoint::osc_thread, this ) )
            FATAL( "Could not create OSC thread" );

/*      lo_server_thread_start( _st ); */

    }

    void
    Endpoint::stop ( void )
    {
        _thread.join();
//    lo_server_thread_stop( _st );
    }

    int
    Endpoint::port ( void ) const
    {
        return lo_server_get_port( _server );
    }

    char *
    Endpoint::url ( void ) const
    {
        return lo_server_get_url( _server );
    }

/** Process any waiting events and return immediately */
    void
    Endpoint::check ( void ) const
    {
        wait( 0 );
    }

/** Process any waiting events and return after timeout */
    void
    Endpoint::wait ( int timeout ) const
    {
        if ( lo_server_wait( _server, timeout ) )
            while ( lo_server_recv_noblock( _server, 0 ) ) { }
    }

/** Process events forever */
    void
    Endpoint::run ( void ) const
    {
        for ( ;; )
        {
            lo_server_recv( _server );
        }
    }

    int
    Endpoint::send ( lo_address to, const char *path, std::list< OSC_Value > values )
    {

        lo_message m = lo_message_new();

        for ( std::list< OSC_Value >::const_iterator i = values.begin();
              i != values.end();
              ++i )
        {
            const OSC_Value *ov = &(*i);

            switch ( ov->type() )
            {
                case 'f':
//                    MESSAGE( "Adding float %f", ((OSC_Float*)ov)->value() );
                    lo_message_add_float( m, ((OSC_Float*)ov)->value() );
                    break;
                case 'i':
//                    MESSAGE( "Adding int %i", ((OSC_Int*)ov)->value() );
                    lo_message_add_int32( m, ((OSC_Int*)ov)->value() );
                    break;
                case 's':
//                    MESSAGE( "Adding string %s", ((OSC_String*)ov)->value() );
                    lo_message_add_string( m, ((OSC_String*)ov)->value() );
                    break;
                default:
                    FATAL( "Unknown format: %c", ov->type() );
                    break;
            }
        }

//        MESSAGE( "Path: %s", path );

        lo_bundle b = lo_bundle_new( LO_TT_IMMEDIATE );

        lo_bundle_add_message(b, path, m );

        int r = lo_send_bundle_from( to, _server, b );

//    int r = lo_send_message_from( to, _server, path, m );

//    lo_message_free( m );

        return r;
    }

    int
    Endpoint::send ( lo_address to, const char *path )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "" );
    }

    int
    Endpoint::send ( lo_address to, const char *path, int v )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "i", v );
    }

    int
    Endpoint::send ( lo_address to, const char *path, float v )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "f", v );
    }

    int
    Endpoint::send ( lo_address to, const char *path, double v )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "d", v );
    }

    int
    Endpoint::send ( lo_address to, const char *path, const char * v )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "s", v );
    }

    int
    Endpoint::send ( lo_address to, const char *path, const char * v1, float v2 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "sf", v1, v2 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, const char * v1, const char *v2 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "ss", v1, v2 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, const char * v1, const char *v2, const char *v3 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "sss", v1, v2, v3 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, const char *v1, int v2, int v3, int v4 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "siii", v1, v2, v3, v4 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, const char *v1, const char *v2, int v3, int v4, int v5 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "ssiii", v1, v2, v3, v4, v5 );
    }


    int
    Endpoint::send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, int v4, int v5, int v6 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "sssiii", v1, v2, v3, v4, v5, v6 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, const char *v1, int v2 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "si", v1, v2 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, int v1, const char *v2 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "is", v1, v2 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, const char *v1, int v2, const char *v3 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "sis", v1, v2, v3 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, int v1, const char *v2, const char *v3, const char *v4 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "isss", v1, v2, v3, v4 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, const char *v1, int v2, const char *v3, const char *v4, const char *v5 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "sisss", v1, v2, v3, v4, v5 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, const char *v4, const char *v5 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "sssss", v1, v2, v3, v4, v5 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, const char *v4 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "ssss", v1, v2, v3, v4 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, lo_message msg )
    {
        return lo_send_message_from( to, _server, path, msg );
    }

    int
    Endpoint::send ( lo_address to, const char *path, const char *v1, const char *v2, int v3, float v4, float v5, float v6 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "ssifff", v1, v2, v3, v4, v5, v6 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, int v4, float v5, float v6, float v7 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "sssifff", v1, v2, v3, v4, v5, v6, v7 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, float v4, float v5, float v6 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "sssfff", v1, v2, v3, v4, v5, v6 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, const char *v1, int v2, int v3 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "sii", v1, v2, v3 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, int v1, int v2 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "ii", v1, v2 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, int v1, float v2 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "if", v1, v2 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, const char *v1, int v2, int v3, float v4 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "siif", v1, v2, v3, v4 );
    }

    int
    Endpoint::send ( lo_address to, const char *path, int v1, int v2, float v3 )
    {
        return lo_send_from( to, _server, LO_TT_IMMEDIATE, path, "iif", v1, v2, v3 );
    }
}
07070100000034000081A40000000000000000000000016258A65C00003A87000000000000000000000000000000000000004000000000new-session-manager-1.6.0+git.20220415.0f6719c/src/Endpoint.hpp
/*******************************************************************************/
/* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager")     */
/* Copyright (C) 2020- Nils Hilbricht                                          */
/*                                                                             */
/* This file is part of New-Session-Manager                                    */
/*                                                                             */
/* New-Session-Manager 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.                                         */
/*                                                                             */
/* New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
/*******************************************************************************/

#pragma once

#include <lo/lo.h>
#include "Thread.hpp"
#include <list>
#include <string>
#include <stdlib.h>
#include <string.h>
#include <map>

namespace OSC
{
    class OSC_Value
    {

    protected:

        char _type;

        float f;
        double d;
        int i;
        const char *s;

    public:

        OSC_Value ( const OSC_Value &rhs )
            {
                _type = rhs._type;

                f =rhs.f;
                d = rhs.d;
                i = rhs.i;
                s = rhs.s;
            }

        OSC_Value ( )
            {
                _type = 0;

                f = 0;
                d = 0;
                i = 0;
                s = 0;
            }

        virtual ~OSC_Value ( ) { }
        virtual char type ( void ) const { return _type; }
    };

    class OSC_Float : public OSC_Value
    {

    public:

        float value ( void ) const { return f; }

        OSC_Float ( float v )
            {
                _type = 'f';
                f = v;
            }
    };

    class OSC_Int : public OSC_Value
    {

    public:

        int value ( void ) const { return i; }

        OSC_Int ( int v )
            {
                _type = 'i';
                i = v;
            }
    };

    class OSC_String : public OSC_Value
    {
    public:

        const char * value ( void ) const { return s; }

        OSC_String ( const char *v )
            {
                _type = 's';
                s = v;
            }
    };

    struct Parameter_Limits
    {
        float min;
        float max;
        float default_value;
    };

    class Endpoint;
    class Signal;
    struct Peer
    {
        bool _scanning;

        char *name;
        lo_address addr;

        std::list<Signal*> _signals;
    };

    typedef int (*signal_handler) ( float value, void *user_data );

    class Signal
    {
//        static int next_id;

    public:

        enum State {
            Created = 0,
            Removed = 1
        };

        enum Direction {
            Input,
            Output,
            Bidirectional
        };

    private:

        Endpoint *_endpoint;

        Peer *_peer;

        char *_path;
        char *_documentation;

        float _value;

        Direction _direction;

        signal_handler _handler;
        void *_user_data;
        Parameter_Limits _parameter_limits;

        void (*_connection_state_callback)(OSC::Signal *, void*);
        void *_connection_state_userdata;

    public:

        const char * peer_name ( void ) const {
            return _peer->name;
        }

        Signal ( const char *path, Direction dir );
        ~Signal ( );

        Direction direction ( void ) const { return _direction; }

        void parameter_limits ( float min, float max, float default_value )
            {
                _parameter_limits.min = min;
                _parameter_limits.max = max;
                _parameter_limits.default_value = default_value;
                _value = default_value;
            }


        void connection_state_callback ( void(*_cb)(OSC::Signal *, void*), void *userdata)
            {
                _connection_state_callback = _cb;
                _connection_state_userdata = userdata;
            }

        const Parameter_Limits& parameter_limits ( void ) const { return _parameter_limits; }

        const char *path ( void ) const { return _path; }

        void rename ( const char *name );

        /* publishes value to targets */
        void value ( float v );
        /* get current value */
        float value ( void ) const { return _value; }

        bool is_connected_to ( const Signal *s ) const;

        friend class Endpoint;
    };

    class Method
    {
        char *_path;
        char *_typespec;
        char *_documentation;

    public:

        const char *path ( void ) { return _path; }
        const char *typespec ( void ) { return _typespec; }

        Method ( );
        ~Method ( );

        friend class Endpoint;
    };


    class Endpoint
    {
        Thread _thread;

        friend class  Signal;

//        lo_server_thread _st;
        lo_server _server;
        lo_address _addr;

        std::list<Peer*> _peers;
        std::list<Signal*> _signals;
        std::list<Method*> _methods;

        char *_learning_path;

        class TranslationDestination {

        public:
            std::string path;
            float current_value;
            bool suppress_feedback;

            TranslationDestination ( )
                {
                    suppress_feedback = false;
                    current_value = -1.0f;
                }
        };

        std::map<std::string,TranslationDestination> _translations;

        void (*_peer_scan_complete_callback)(void*);
        void *_peer_scan_complete_userdata;

        char *_name;

        static void error_handler(int num, const char *msg, const char *path);

        static int osc_reply ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data );

        static int osc_signal_lister ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data );
        static int osc_generic ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data );
        static int osc_sig_handler ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data );
        static int osc_sig_renamed ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data );
        static int osc_sig_removed ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data );
        static int osc_sig_created ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data );
        static int osc_sig_disconnect ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data );
        static int osc_sig_connect ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data );
        static int osc_sig_hello ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data );


        Peer * add_peer ( const char *name, const char *url );
        void scan_peer ( const char *name, const char *url );

    private:

        static void *osc_thread ( void *arg );
        void osc_thread ( void );

        OSC::Signal *find_peer_signal_by_path ( Peer *p, const char *path );
        OSC::Signal *find_signal_by_path ( const char *path );

        Peer *find_peer_by_name ( const char *name );
        Peer *find_peer_by_address ( lo_address addr );
        static bool address_matches ( lo_address addr1, lo_address addr2 );

        static Signal *find_target_by_peer_address ( std::list<Signal*> *l, lo_address addr );

        void del_signal ( Signal *signal );
        void send_signal_rename_notifications( Signal *s );


        void (*_peer_signal_notification_callback)( OSC::Signal *,  OSC::Signal::State, void*);
        void *_peer_signal_notification_userdata;

    public:

        void send_feedback ( const char *path, float v );
        void learn ( const char *path );

        lo_address address ( void )
            {
                return _addr;
            }

        const char * * get_connections ( const char *path );
        void clear_translations ( void );
        void del_translation ( const char *a );
        void add_translation ( const char *a, const char *b );
        void rename_translation_destination ( const char *a, const char *b );
        void rename_translation_source ( const char *a, const char *b );
        int ntranslations ( void );
        bool get_translation ( int n, const char **from, const char **to );

        void peer_signal_notification_callback ( void (*cb)(OSC::Signal *, OSC::Signal::State, void*), void *userdata )
            {
                _peer_signal_notification_callback = cb;
                _peer_signal_notification_userdata = userdata;
            }

        // can be used to point back to owning object.
        void *owner;

        void list_peer_signals ( void *v );

        int init ( int proto, const char *port = 0 );

        Endpoint ( );

        ~Endpoint ( );

        bool disconnect_signal ( OSC::Signal *s, OSC::Signal *d );
        bool disconnect_signal ( OSC::Signal *s, const char *signal_path );
        bool connect_signal ( OSC::Signal *s, OSC::Signal *d );
        bool connect_signal ( OSC::Signal *s, const char *peer_name, const char *signal_path );
//        bool connect_signal ( OSC::Signal *s, const char *peer_name, int signal_id );
        bool connect_signal ( OSC::Signal *s, const char *peer_and_path );

        Signal * add_signal ( const char *path, Signal::Direction dir, float min, float max, float default_value, signal_handler handler, void *user_data );
        Method *add_method ( const char *path, const char *typespec, lo_method_handler handler, void *user_data, const char *argument_description );
        void del_method ( const char *path, const char *typespec );
        void del_method ( Method* method );
        void start ( void );
        void stop ( void );
        int port ( void ) const;
        char * url ( void ) const;

        void check ( void ) const;
        void wait ( int timeout ) const;
        void run ( void ) const;

        void name ( const char *name ) { _name = strdup( name ); }
        const char *name ( void ) { return _name; }

        void hello ( const char *url );
        void handle_hello ( const char *peer_name, const char *peer_url );

        int send ( lo_address to, const char *path, std::list< OSC_Value > values );

        /* overloads for common message formats */
        int send ( lo_address to, const char *path );
        int send ( lo_address to, const char *path, float v );
        int send ( lo_address to, const char *path, double v );
        int send ( lo_address to, const char *path, int v );
        int send ( lo_address to, const char *path, long v );
        int send ( lo_address to, const char *path, int v1, int v2 );
        int send ( lo_address to, const char *path, int v1, float v2 );
        int send ( lo_address to, const char *path, int v1, int v2, float v3 );
        int send ( lo_address to, const char *path, const char *v );
        int send ( lo_address to, const char *path, const char *v1, float v2 );
        int send ( lo_address to, const char *path, const char *v1, int v2, int v3 );
        int send ( lo_address to, const char *path, const char *v1, const char *v2 );
        int send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3 );
        int send ( lo_address to, const char *path, const char *v1, int v2, int v3, int v4 );
        int send ( lo_address to, const char *path, const char *v1, const char *v2, int v3, int v4, int v5 );

        int send ( lo_address to, const char *path, const char *v1, int v2 );
        int send ( lo_address to, const char *path, int v1, const char *v2 );
        int send ( lo_address to, const char *path, const char *v1, int v2, int v3, float v4 );

        int send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, int v4, int v5, int v6 );
        int send ( lo_address to, const char *path, const char *v1, int v2, const char *v3 );
        int send ( lo_address to, const char *path, int v1, const char *v2, const char *v3, const char *v4 );
        int send ( lo_address to, const char *path, const char *v1, int v2, const char *v3, const char *v4, const char *v5 );
        int send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, const char *v4, const char *v5 );
        int send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, const char *v4 );

        int send ( lo_address to, const char *path, lo_message msg );

        int send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, float v4, float v5, float v6 );

        int send ( lo_address to, const char *path, const char *v1, const char *v2, int v3, float v4, float v5, float v6 );

        int send ( lo_address to, const char *path, const char *v1, const char *v2, const char *v3, int v4, float v5, float v6, float v7 );

        void peer_scan_complete_callback ( void(*_cb)(void*), void *userdata)
            {
                _peer_scan_complete_callback = _cb;
                _peer_scan_complete_userdata = userdata;
            }


        friend Signal::~Signal();
        friend void Signal::rename ( const char *name );
    };

}

/* helper macros for defining OSC handlers */
/* #define OSC_NAME( name ) osc_ ## name */
#define OSC_DMSG() MESSAGE( "Got OSC message: %s", path );
// #define OSC_HANDLER( name ) static int OSC_NAME( name ) ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )


/* #define OSC_REPLY_OK() ((OSC::Endpoint*)user_data)->send( lo_message_get_source( msg ), path, "ok" ) */
/* #define OSC_REPLY( value ) ((OSC::Endpoint*)user_data)->send( lo_message_get_source( msg ), path, value ) */
/* #define OSC_REPLY_ERR() ((OSC::Endpoint*)user_data)->send( lo_message_get_source( msg ), path, "err" ) */
/* #define OSC_ENDPOINT() ((OSC::Endpoint*)user_data) */
07070100000035000041ED0000000000000000000000026258A65C00000000000000000000000000000000000000000000003600000000new-session-manager-1.6.0+git.20220415.0f6719c/src/FL07070100000036000081A40000000000000000000000016258A65C00001B1D000000000000000000000000000000000000004800000000new-session-manager-1.6.0+git.20220415.0f6719c/src/FL/Fl_Packscroller.H
/*******************************************************************************/
/* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager")     */
/* Copyright (C) 2020- Nils Hilbricht                                          */
/*                                                                             */
/* This file is part of New-Session-Manager                                    */
/*                                                                             */
/* New-Session-Manager 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.                                         */
/*                                                                             */
/* New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
/*******************************************************************************/

/* Scrolling group suitable for containing a single child (a
 * pack). When the Fl_Packscroller is resized, the child will be resized
 * too. No scrollbars are displayed, but the widget responds to
 * FL_MOUSEWHEEL events. */

#pragma once

#include <FL/Fl_Group.H>
#include <FL/fl_draw.H>
#include <FL/Fl.H>

/* FIXME: Optimize scroll */

class Fl_Packscroller : public Fl_Group
{
    int _increment;
    int _yposition;
    static const int sbh = 15;                                  /* scroll button height */

public:

    Fl_Packscroller ( int X, int Y, int W, int H, const char *L = 0 ) : Fl_Group( X, Y, W, H, L )
        {
            _increment = 30;
            _yposition = 0;
//            color( FL_WHITE );
        }

    int increment ( void ) const { return _increment; }
    void increment ( int v ) { _increment = v; }

    void yposition ( int v )
        {
            if ( ! children() )
                return;

            int Y = v;

            if ( Y > 0 )
                Y = 0;

            const int H = h();
// - (sbh * 2);

            Fl_Widget *o = child( 0 );

            if ( o->h() > H &&
                 Y + o->h() < H )
                Y = H - o->h();
            else if ( o->h() < H )
                Y = 0;

            if ( _yposition != Y )
            {
                _yposition = Y;

                damage( FL_DAMAGE_SCROLL );
            }
        }

    int yposition ( void ) const
        {
            if ( children() )
                return child( 0 )->y() - (y() + sbh);

            return 0;
        }

    void bbox ( int &X, int &Y, int &W, int &H )
        {
            X = x();
            Y = y() + ( sbh * top_sb_visible() );
            W = w();
            H = h() - ( sbh * ( top_sb_visible() + bottom_sb_visible() ) );
        }

    int top_sb_visible ( void )
        {
            return children() && child(0)->y() != y() ? 1 : 0;
        }

    int bottom_sb_visible ( void )
        {
            if ( children() )
            {
                Fl_Widget *o = child( 0 );
           
                if ( o->h() > h() && o->y() + o->h() != y() + h() )
                    return 1;
            }

            return 0;
        }

    virtual void
    draw ( void )
        {
            if ( damage() & FL_DAMAGE_ALL )
            {
                fl_rectf( x(), y(), w(), h(), color() );
            }

            if ( ! children() )
                return;

            Fl_Widget *o = child( 0 );

            o->position( x(), y() + _yposition );

            const int top_sb = top_sb_visible();
            const int bottom_sb = bottom_sb_visible();
            
            fl_push_clip( x(), y() + ( sbh * top_sb ), w(), h() - (sbh * (top_sb + bottom_sb) ));
            
            draw_children();

            fl_pop_clip();
            
            fl_font( FL_HELVETICA, 12 );
            
            if ( top_sb )
            {
                fl_draw_box( box(), x(), y(), w(), sbh, color() );
                fl_color( fl_contrast( FL_FOREGROUND_COLOR, color() ) );
                fl_draw( "@2<", x(), y(), w(), sbh, FL_ALIGN_CENTER );
            }
            
            if ( bottom_sb )
            {
                fl_draw_box( box(), x(), y() + h() - sbh, w(), sbh, color() );
                fl_color( fl_contrast( FL_FOREGROUND_COLOR, color() ) );
                fl_draw( "@2>", x(), y() + h() - sbh, w(), sbh, FL_ALIGN_CENTER );
            }
        }

    virtual int
    handle ( int m )
        {
            switch ( m )
            {
                case FL_PUSH:
                    if ( top_sb_visible() &&
                         Fl::event_inside( x(), y(), w(), sbh ) )
                    {
                        return 1;
                    }
                    else if ( bottom_sb_visible() &&
                              Fl::event_inside(  x(), y() + h() - sbh, w(), sbh ) )
                    {
                        return 1;
                    }
                    break;
                case FL_RELEASE:
                {
                    if ( top_sb_visible() &&
                         Fl::event_inside( x(), y(), w(), sbh ) )
                    {
                        yposition( yposition() + ( h() / 4 ) );
                        return 1;
                    }
                    else if ( bottom_sb_visible() &&
                              Fl::event_inside(  x(), y() + h() - sbh, w(), sbh ) )
                    {
                        yposition( yposition() - (h() / 4 ) );
                        return 1;
                    }
                    break;
                }
                case FL_KEYBOARD:
                {
                    if ( Fl::event_key() == FL_Up )
                    {
                        yposition( yposition() + ( h() / 4 ) );
                        return 1;
                    }
                    else if ( Fl::event_key() == FL_Down )
                    {
                        yposition( yposition() - (h() / 4 ) );
                        return 1;
                    }
                    break;
                }
                case FL_MOUSEWHEEL:
                {
                    yposition( yposition() - ( Fl::event_dy() * _increment ) );

                    return 1;
                }
            }

            return Fl_Group::handle( m );
        }
};
07070100000037000081A40000000000000000000000016258A65C00001C36000000000000000000000000000000000000004500000000new-session-manager-1.6.0+git.20220415.0f6719c/src/FL/Fl_Scalepack.C
/*******************************************************************************/
/* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager")     */
/* Copyright (C) 2020- Nils Hilbricht                                          */
/*                                                                             */
/* This file is part of New-Session-Manager                                    */
/*                                                                             */
/* New-Session-Manager 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.                                         */
/*                                                                             */
/* New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
/*******************************************************************************/


/* Fl_Scalepack

   This is similar to an Fl_Pack, but instead of the pack resizing
   itself to enclose its children, this pack resizes its children to
   fit itself. Of course, this only works well with highly flexible
   widgets, but the task comes up often enough to warrent this class.

   If and child happens to be the resizable() widget, then it will be
   resized so the all the other children can fit around it, with their
   current sizes (and the size of the Fl_Scalepack) maintained.

   NOTES: An Fl_Pack as a direct child will not work, because Fl_Pack
   changes its size in draw(), which throws off our resize
   calculation. The whole idea of widgets being able to resize
   themselves within draw() is horribly broken...
*/

#include "Fl_Scalepack.H"

#include <FL/Fl.H>
#include <FL/fl_draw.H>
#include <stdio.h>

Fl_Scalepack::Fl_Scalepack ( int X, int Y, int W, int H, const char *L ) :
    Fl_Group( X, Y, W, H, L )
{
    resizable( 0 );
    _spacing = 0;
}

void
Fl_Scalepack::resize ( int X, int Y, int W, int H )
{
    /* Fl_Group's resize will change our child widget sizes, which
     interferes with our own resizing method. */
    long dx = X - x();
    long dy = Y - y();
    
    bool r = W != w() || H != h();

    Fl_Widget::resize( X, Y, W, H );

    Fl_Widget*const* a = array();

    for (int i=children(); i--;)
    {
        Fl_Widget* o = *a++;

        o->position( o->x() + dx, o->y() + dy );
    }

    if ( r )
        redraw();
}

void
Fl_Scalepack::draw ( void )
{

    if ( resizable() == this )
        /* this resizable( this ) is the default for Fl_Group and is
         * reset by Fl_Group::clear(), but it is not our default... */
        resizable( 0 );

    int tx = x() + Fl::box_dx( box() );
    int ty = y() + Fl::box_dy( box() );
    int tw = w() - Fl::box_dw( box() );
    int th = h() - Fl::box_dh( box() );

    if ( damage() & FL_DAMAGE_ALL )
    {
        draw_box();

        draw_label();
    }

    int v = 0;

    int cth = 0;
    int ctw = 0;

    Fl_Widget * const * a = array();

    for ( int i = children(); i--; )
    {
        Fl_Widget *o = *a++;

        if ( o->visible() )
        {
            ++v;

            if ( o != this->resizable() )
            {
                cth += o->h();
                ctw += o->w();
            }

            cth += _spacing;
            ctw += _spacing;
        }
    }

    ctw -= _spacing;
    cth -= _spacing;

    if ( 0 == v )
        return;

    if ( this->resizable() )
    {
        int pos = 0;

        Fl_Widget * const * a = array();

        for ( int i = children(); i--; )
        {
            Fl_Widget *o = *a++;

            if ( o->visible() )
            {
                int X, Y, W, H;

                if ( type() == HORIZONTAL )
                {
                    X = tx + pos;
                    Y = ty;
                    W = o->w();
                    H = th;
                }
                else
                {
                    X = tx;
                    Y = ty + pos;
                    W = tw;
                    H = o->h();
                }

                if ( this->resizable() == o )
                {
                    if ( type() == HORIZONTAL )
                        W = tw - ctw - 3;
                    else
                        H = th - cth;
                }

                if (X != o->x() || Y != o->y() || W != o->w() || H != o->h() )
                {
                    o->resize(X,Y,W,H);
                    o->clear_damage(FL_DAMAGE_ALL);
                }

                if ( damage() & FL_DAMAGE_ALL )
                {
                    draw_child( *o );
                    draw_outside_label( *o );
                }
                else
                    update_child( *o );

/*                 if ( this->resizable() == o ) */
/*                     fl_rect( o->x(), o->y(), o->w(), o->h(), type() == VERTICAL ? FL_GREEN : FL_BLUE ); */

                if ( type() == HORIZONTAL )
                    pos += o->w() + spacing();
                else
                    pos += o->h() + spacing();

            }
        }
    }
    else
    {
        int sz = 0;
        int pos = 0;

        if ( type() == HORIZONTAL )
            sz = (tw - (_spacing * (v - 1))) / v;
        else
            sz = (th - (_spacing * (v - 1))) / v;

        Fl_Widget * const * a = array();

        for ( int i = children(); i--; )
        {
            Fl_Widget *o = *a++;

            if ( o->visible() )
            {
                int X, Y, W, H;

                if ( type() == HORIZONTAL )
                {
                    X = tx + pos;
                    Y = ty;
                    W = sz;
                    H = th;
                }
                else
                {
                    X = tx;
                    Y = ty + pos;
                    W = tw;
                    H = sz;
                }

                if (X != o->x() || Y != o->y() || W != o->w() || H != o->h() )
                {
                    o->resize(X,Y,W,H);
                    o->clear_damage(FL_DAMAGE_ALL);
                }

                if ( damage() & FL_DAMAGE_ALL )
                {
                    draw_child( *o );
                    draw_outside_label( *o );
                }
                else
                    update_child( *o );

//                    fl_rect( o->x(), o->y(), o->w(), o->h(), type() == VERTICAL ? FL_RED : FL_YELLOW );

                if ( type() == HORIZONTAL )
                    pos += o->w() + spacing();
                else
                    pos += o->h() + spacing();

            }
        }
    }
}
07070100000038000081A40000000000000000000000016258A65C000007D0000000000000000000000000000000000000004500000000new-session-manager-1.6.0+git.20220415.0f6719c/src/FL/Fl_Scalepack.H
/*******************************************************************************/
/* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager")     */
/* Copyright (C) 2020- Nils Hilbricht                                          */
/*                                                                             */
/* This file is part of New-Session-Manager                                    */
/*                                                                             */
/* New-Session-Manager 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.                                         */
/*                                                                             */
/* New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
/*******************************************************************************/

#pragma once

#include <FL/Fl_Group.H>

class Fl_Scalepack : public Fl_Group
{

    int _spacing;

public:

    enum { VERTICAL, HORIZONTAL };

    Fl_Scalepack ( int X, int Y, int W, int H, const char *L = 0 );
    virtual ~Fl_Scalepack ( ) { }

    int spacing ( void ) const { return _spacing; }
    void spacing ( int v ) { _spacing = v; redraw(); }

    virtual void resize ( int, int, int, int );

    virtual void draw ( void );

};
07070100000039000081A40000000000000000000000016258A65C00001290000000000000000000000000000000000000004300000000new-session-manager-1.6.0+git.20220415.0f6719c/src/NSM_Proxy_UI.fl# data file for the Fltk User Interface Designer (fluid)
version 1.0305
header_name {.H}
code_name {.C}
class NSM_Proxy_UI {open
} {
  Function {make_window()} {open
  } {
    Fl_Window {} {
      label {NSM Proxy} open
      xywh {720 472 675 505} type Double color 47 labelcolor 55 xclass {NSM-Proxy} visible
    } {
      Fl_Return_Button start_button {
        label Start
        xywh {285 460 88 25}
      }
      Fl_Button kill_button {
        label Kill
        xywh {150 460 80 25} color 72 hide
      }
      Fl_Tabs {} {open
        xywh {0 0 678 445}
      } {
        Fl_Group {} {
          label Run open
          xywh {25 46 635 359}
        } {
          Fl_Box {} {
            label {NSM-Proxy handles clients without direct NSM support.
            It should not be used to start real NSM clients.

Command-line options MUST go in the "Arguments" field.

 The program will be started with its current working directory being a uniquely named directory under the current session directory. It is recommended that you only refer to files as arguments in this directory to guarantee a transportable and archivable session.
} selected
            xywh {28 87 612 150} box BORDER_BOX color 41 labelfont 8 labelsize 12 labelcolor 55 align 128
          }
          Fl_Input arguments_input {
            label {Arguments:}
            tooltip {Command line parameter like --verbose} xywh {170 285 345 25}
          }
          Fl_Input label_input {
            label {NSM GUI Label:}
            tooltip {Will show in your NSM GUI, useful if you have multiple NSM-Proxy clients in one session} xywh {170 324 345 26}
          }
          Fl_Input executable_input {
            label {Executable Name:}
            tooltip {The pure name of the executable. Just xterm not /usr/bin/xterm} xywh {170 249 345 26}
          }
        }
        Fl_Group {} {
          label Advanced open
          xywh {12 31 663 414} hide
        } {
          Fl_Choice save_signal_choice {
            label {Save Signal:} open
            xywh {246 400 170 25} down_box BORDER_BOX
          } {
            MenuItem {} {
              label None
              xywh {10 10 40 24}
            }
            MenuItem {} {
              label SIGUSR1
              xywh {20 20 40 24}
            }
            MenuItem {} {
              label SIGUSR2
              xywh {30 30 40 24}
            }
            MenuItem {} {
              label SIGINT
              xywh {40 40 40 24}
            }
          }
          Fl_Box {} {
            label {The environment variables $NSM_CLIENT_ID and $NSM_SESSION_NAME will contain the unique client ID (suitable for use as e.g. a JACK client name) and the display name for the session, respectively. The variable $CONFIG_FILE will contain the name of the nsm-proxy config file.}
            xywh {26 46 610 84} box BORDER_BOX color 41 labelfont 8 labelsize 12 labelcolor 55 align 128
          }
          Fl_Box {} {
            label {Very few programs may respond to a specific Unix signal by somehow saving their state. If 'Save Signal' is set to something other than 'None', then NSM Proxy will deliver the specified signal to the proxied process upon an NSM 'Save' event. Most programs will treat these signals just like SIGTERM and die. NSM-Proxy cannot force a program to save in our session directory.  }
            xywh {26 306 610 79} box BORDER_BOX color 41 labelfont 8 labelsize 12 labelcolor 55 align 128
          }
          Fl_Choice stop_signal_choice {
            label {Stop Signal:} open
            xywh {246 255 170 25} down_box BORDER_BOX
          } {
            MenuItem {} {
              label SIGTERM
              xywh {20 20 40 24}
            }
            MenuItem {} {
              label SIGINT
              xywh {50 50 40 24}
            }
            MenuItem {} {
              label SIGHUP
              xywh {60 60 40 24}
            }
          }
          Fl_Box {} {
            label {Most programs will shutdown gracefully when sent a SIGTERM or SIGINT signal. It's impossible to know which signal a specific program will respond to. A unhandled signal will simply kill the process, and may cause problems with the audio subsystem (e.g. JACK). Check the program's documentation or source code to determine which signal to use to stop it gracefully.}
            xywh {26 166 610 79} box BORDER_BOX color 41 labelfont 8 labelsize 12 labelcolor 55 align 128
          }
          Fl_File_Input config_file_input {
            label {(Hidden!) Config File:}
            xywh {158 409 406 31} hide
          }
          Fl_Button config_file_browse_button {
            label Browse
            xywh {573 410 85 25} hide
          }
        }
      }
    }
  }
}
0707010000003A000081A40000000000000000000000016258A65C00000E16000000000000000000000000000000000000003E00000000new-session-manager-1.6.0+git.20220415.0f6719c/src/Thread.cpp
/*******************************************************************************/
/* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager")     */
/* Copyright (C) 2020- Nils Hilbricht                                          */
/*                                                                             */
/* This file is part of New-Session-Manager                                    */
/*                                                                             */
/* New-Session-Manager 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.                                         */
/*                                                                             */
/* New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
/*******************************************************************************/

#include "Thread.hpp"
#include <assert.h>
#include <string.h>



pthread_key_t Thread::_current = 0;



Thread::Thread ( )
{
    _thread = 0;
    _running = false;
    _name = 0;
}

Thread::Thread ( const char *name )
{
    _thread = 0;
    _running = false;
    _name = name;
}

void
Thread::init ( void )
{
    pthread_key_create( &_current, NULL );
}

bool
Thread::is ( const char *name )
{
    return ! strcmp( Thread::current()->name(), name );
}

/** to be used by existing threads (that won't call clone()) */
void
Thread::set ( const char *name )
{
    _thread = pthread_self();
    _name = name;
    _running = true;

    pthread_setspecific( _current, (void*)this );
}

Thread *
Thread::current ( void )
{
    return (Thread*)pthread_getspecific( _current );
}


struct thread_data
{
    void *(*entry_point)(void *);
    void *arg;
    void *t;
};

void *
Thread::run_thread ( void *arg )
{
    thread_data td = *(thread_data *)arg;
    delete (thread_data*)arg;

    pthread_setspecific( _current, td.t );

    ((Thread*)td.t)->_running = true;

    void * r = td.entry_point( td.arg );

    ((Thread*)td.t)->_running = false;

    return r;
}


bool
Thread::clone ( void *(*entry_point)(void *), void *arg )
{
    assert( ! _thread );

    thread_data *td = new thread_data;
    td->entry_point = entry_point;
    td->arg = arg;
    td->t = this;

    if ( pthread_create( &_thread, NULL, run_thread, td ) != 0 )
        return false;

    return true;
}

void
Thread::detach ( void )
{
    pthread_detach( _thread );
    _thread = 0;
}

void
Thread::cancel ( void )
{
    pthread_cancel( _thread );
    _thread = 0;
}

void
Thread::join ( void )
{
    if ( _thread != 0 )
    {
        /* not joined yet, go ahead. */
        pthread_join( _thread, NULL );
    }
    _thread = 0;
}

/* never call this unless some other thread will be calling 'join' on
 * this one, otherwise, running() will return true even though the
 * thread is dead */
void
Thread::exit ( void *retval )
{
    _running = false;
    pthread_exit( retval );
}
0707010000003B000081A40000000000000000000000016258A65C000009FD000000000000000000000000000000000000003E00000000new-session-manager-1.6.0+git.20220415.0f6719c/src/Thread.hpp
/*******************************************************************************/
/* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager")     */
/* Copyright (C) 2020- Nils Hilbricht                                          */
/*                                                                             */
/* This file is part of New-Session-Manager                                    */
/*                                                                             */
/* New-Session-Manager 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.                                         */
/*                                                                             */
/* New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
/*******************************************************************************/

#pragma once

/* simple wrapper for pthreads with thread role checking */
#include <pthread.h>

#define THREAD_ASSERT( n ) ASSERT( Thread::is( #n ), "Function called from wrong thread! (is %s, should be %s)", Thread::current()->name(), #n )

class Thread
{
    static pthread_key_t _current;

    pthread_t _thread;
    const char * _name;

    volatile bool _running;

    static void * run_thread ( void *arg );

public:

    static bool is ( const char *name );

    static void init ( void );
    static Thread *current ( void );

    Thread ( );
    Thread ( const char *name );

    const char *name ( void ) const { return _name; }
    void name ( const char *name ) { _name = name; }

    bool running ( void ) const { return _running; }
    void set ( const char *name );
    void set ( void ) { set( _name ); }
    bool clone ( void *(*entry_point)(void *), void *arg );
    void detach ( void );
    void join ( void );
    void cancel ( void );
    void exit ( void *retval = 0 );

};
0707010000003C000081A40000000000000000000000016258A65C00000A62000000000000000000000000000000000000003D00000000new-session-manager-1.6.0+git.20220415.0f6719c/src/debug.cpp
/*******************************************************************************/
/* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager")     */
/* Copyright (C) 2020- Nils Hilbricht                                          */
/*                                                                             */
/* This file is part of New-Session-Manager                                    */
/*                                                                             */
/* New-Session-Manager 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.                                         */
/*                                                                             */
/* New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
/*******************************************************************************/


#include "debug.h"

#include <string.h>
#include <stdio.h>
#include <stdarg.h>

extern char * program_invocation_short_name;

bool quietMessages = false;

void
warnf ( warning_t level,
       const char *module,
       const char *file,
       const char *function, int line, const char *fmt, ... )
{
    va_list args;
    static const char *level_tab[] = {
        "message", "",
        "warning", "",
        "assertion", ""
    };

        module = program_invocation_short_name;

    if ( module )
        fprintf( stderr, "[%s] ", module );
#ifndef NDEBUG
    if ( file )
        fprintf( stderr, "%s", file );
    if ( line )
        fprintf( stderr, ":%i", line );
    if ( function )
        fprintf( stderr, " %s()", function );

    fprintf( stderr, ": " );
#endif

    if ( unsigned( ( level << 1 ) + 1 ) <
         ( sizeof( level_tab ) / sizeof( level_tab[0] ) ) )
        fprintf( stderr, "%s", level_tab[( level << 1 ) + 1] );

    if ( fmt )
    {
        va_start( args, fmt );
        vfprintf( stderr, fmt, args );
        va_end( args );
    }

    fprintf( stderr, "\n" );
}
0707010000003D000081A40000000000000000000000016258A65C000011A3000000000000000000000000000000000000003B00000000new-session-manager-1.6.0+git.20220415.0f6719c/src/debug.h
/*******************************************************************************/
/* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager")     */
/* Copyright (C) 2020- Nils Hilbricht                                          */
/*                                                                             */
/* This file is part of New-Session-Manager                                    */
/*                                                                             */
/* New-Session-Manager 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.                                         */
/*                                                                             */
/* New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
/*******************************************************************************/

/* debug.h
 *
 * 11/21/2003 - Jonathan Moore Liles
 *
 * Debuging support.
 *
 * Disable by defining the preprocessor variable NDEBUG prior to inclusion.
 *
 * The following macros sould be defined as string literals
 *
 *  name            value
 *
 *  __MODULE__      Name of module. eg. "libfoo"
 *
 *  __FILE__        Name of file. eg. "foo.c"
 *
 *  __FUNCTION__        Name of enclosing function. eg. "bar"
 *
 *  (inteter literal)
 *  __LINE__        Number of enclosing line.
 *
 *
 * __FILE__, and __LINE__ are automatically defined by standard CPP
 * implementations. __FUNCTION__ is more or less unique to GNU, and isn't
 * strictly a preprocessor macro, but rather a reserved word in the compiler.
 * There is a sed script available with this toolset that is able to fake
 * __FUNCTION__ (among other things) with an extra preprocesessing step.
 *
 * __MODULE__ is nonstandard and should be defined the enclosing program(s).
 * Autoconf defines PACKAGE as the module name, and these routines will use its
 * value instead if __MODULE__ is undefined.
 *
 * The following routines are provided (as macros) and take the same arguments
 * as printf():
 *
 * MESSAGE( const char *format, ... )
 * WARNING( const char *format, ... )
 * FATAL( const char *format, ... )
 *
 * Calling MESSAGE or WARNING prints the message to stderr along with module,
 * file and line information, as well as appropriate emphasis. Calling
 * FATAL will do the same, and then call abort() to end the program. It is
 * unwise to supply any of these marcros with arguments that produce side
 * effects. As, doing so will most likely result in Heisenbugs; program
 * behavior that changes when debugging is disabled.
 *
 */




#ifndef _DEBUG_H
#define _DEBUG_H

#ifndef __MODULE__
#ifdef PACKAGE
#define __MODULE__ PACKAGE
#else
#define __MODULE__ NULL
#endif
#endif

#ifndef __GNUC__
    #define __FUNCTION__ NULL
#endif

extern bool quietMessages;

typedef enum {
    W_MESSAGE = 0,
    W_WARNING,
    W_FATAL
} warning_t;

void
warnf ( warning_t level,
       const char *module,
       const char *file,
        const char *function, int line, const char *fmt, ... );

//We do not use NDEBUG anymore. Messages are a command line switch.
//Warnings, asserts and errors are always important.

// #define MESSAGE( fmt, args... ) warnf( W_MESSAGE, __MODULE__, __FILE__, __FUNCTION__, __LINE__, fmt, ## args )
#define MESSAGE( fmt, args... ) do { if ( ! (quietMessages) ) { warnf( W_MESSAGE, __MODULE__, __FILE__, __FUNCTION__, __LINE__, fmt, ## args ); } } while ( 0 )
#define WARNING( fmt, args... ) warnf( W_WARNING, __MODULE__, __FILE__, __FUNCTION__, __LINE__, fmt, ## args )
#define FATAL( fmt, args... ) ( warnf( W_FATAL, __MODULE__, __FILE__, __FUNCTION__, __LINE__, fmt, ## args ), abort() )
#define ASSERT( pred, fmt, args... ) do { if ( ! (pred) ) { warnf( W_FATAL, __MODULE__, __FILE__, __FUNCTION__, __LINE__, fmt, ## args ); abort(); } } while ( 0 )

#endif
0707010000003E000081A40000000000000000000000016258A65C00001413000000000000000000000000000000000000003C00000000new-session-manager-1.6.0+git.20220415.0f6719c/src/file.cpp
/*******************************************************************************/
/* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager")     */
/* Copyright (C) 2020- Nils Hilbricht                                          */
/*                                                                             */
/* This file is part of New-Session-Manager                                    */
/*                                                                             */
/* New-Session-Manager 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.                                         */
/*                                                                             */
/* New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
/*******************************************************************************/

#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/vfs.h>

unsigned long
modification_time ( const char *file )
{
    struct stat st;

    if ( stat( file, &st ) )
        return 0;

    return st.st_mtime;
}

/** returns /true/ if /file1/ is newer than /file2/ (or file2 doesn't exist) */
bool
newer ( const char *file1, const char *file2 )
{
    return modification_time( file1 ) > modification_time( file2 );
}

unsigned long
size ( const char *file )
{
    struct stat st;

    if ( stat( file, &st ) )
        return 0;

    return st.st_size;
}

int
exists ( const char *name )
{
    struct stat st;

    return 0 == stat( name, &st );
}


char *
simple_hash( const char *s )
{   //djb2
    unsigned long hashAddress = 5381;
    for ( int counter = 0;  s[counter]!='\0'; counter++ ) {
        hashAddress = ( (hashAddress << 5) + hashAddress ) + s[counter];
    }

    char *result = NULL;
    asprintf( &result, "%lu", hashAddress % 65521 );
    return result;
}

int
backwards_fgetc ( FILE *fp )
{
    int c;

    if ( fseek( fp, -1, SEEK_CUR ) != 0 )
        return -1;

    c = fgetc( fp );

    fseek( fp, -1, SEEK_CUR );

    return c;
}

char *
backwards_afgets ( FILE *fp )
{
    size_t size = 0;

    char *s = NULL;
    size_t i = 0;
    int c;
    while ( ( c = backwards_fgetc( fp ) ) >= 0 )
    {
        if ( i > 0 && '\n' == c )
            break;

        if ( i >= size )
        {
            size += 256;
            s = (char*)realloc( s, size );
        }

        s[i++] = c;

    }

    if ( s )
    {
        s[i] = 0;

        int len = strlen(s) ;
        int c, i, j;

        for (i = 0, j = len - 1; i < j; i++, j--)
        {
            c = s[i];
            s[i] = s[j];
            s[j] = c;
        }
    }

    fseek( fp, 1, SEEK_CUR );

    return s;
}


/** update the modification time of file referred to by /fd/ */
void
touch ( int fd )
{
    struct stat st;

    fstat( fd, &st );

    fchmod( fd, st.st_mode );
}

/** write a single string to a file */
void
write_line ( const char *dir, const char *name, const char *value )
{
    char path[512];

    snprintf( path, sizeof( path ), "%s/%s", dir, name );

    FILE *fp = fopen( path, "w" );

    if ( ! fp )
        return;

    fputs( value, fp );

    fclose( fp );
}

/** read a single string from a file */
char *
read_line ( const char *dir, const char *name  )
{
    char path[512];

    snprintf( path, sizeof( path ), "%s/%s", dir, name );

    FILE *fp = fopen( path, "r" );

    if ( ! fp )
        return 0;

    char *value = (char*)malloc( 512 );

    if ( ! fgets( value, 512, fp ) )
        value[0] = 0;

    fclose( fp );

    return value;
}

#include <sys/statvfs.h>

/** return the number of blocks free on filesystem containing file named /file/ */
fsblkcnt_t
free_space ( const char *file )
{
    struct statfs st;
    memset( &st, 0, sizeof( st ) );

    statfs( file, &st );

    return st.f_bavail;
}

/** return the total number of blocks on filesystem containing file named /file/ */
fsblkcnt_t
total_space ( const char *file )
{
    struct statfs st;
    memset( &st, 0, sizeof( st ) );

    statfs( file, &st );

    return st.f_blocks;
}

/** return the percentage of usage on filesystem containing file named /file/ */
int
percent_used ( const char *file )
{
    const double ts = total_space( file );
    const double fs = free_space( file );

    double percent_free = ( ( fs / ts ) * 100.0f );

    return (int) (100.0f - percent_free);
}
0707010000003F000081A40000000000000000000000016258A65C0000085F000000000000000000000000000000000000003A00000000new-session-manager-1.6.0+git.20220415.0f6719c/src/file.h
/*******************************************************************************/
/* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager")     */
/* Copyright (C) 2020- Nils Hilbricht                                          */
/*                                                                             */
/* This file is part of New-Session-Manager                                    */
/*                                                                             */
/* New-Session-Manager 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.                                         */
/*                                                                             */
/* New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
/*******************************************************************************/

#include <stdio.h>

unsigned long modification_time ( const char *file );

bool newer ( const char *file1, const char *file2 );
unsigned long size ( const char *file );
int exists ( const char *name );
char * simple_hash( const char *s);
int backwards_fgetc ( FILE *fp );
char * backwards_afgets ( FILE *fp );
void touch ( int fd );
void write_line ( const char *dir, const char *name, const char *value );
char * read_line ( const char *dir, const char *name );
size_t free_space ( const char *file );
size_t total_space ( const char *file );
int percent_used ( const char *file );
07070100000040000081A40000000000000000000000016258A65C00006C54000000000000000000000000000000000000003F00000000new-session-manager-1.6.0+git.20220415.0f6719c/src/jackpatch.c
/*******************************************************************************/
/* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager")     */
/* Copyright (C) 2020- Nils Hilbricht                                          */
/*                                                                             */
/* This file is part of New-Session-Manager                                    */
/*                                                                             */
/* New-Session-Manager 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.                                         */
/*                                                                             */
/* New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
/*******************************************************************************/

/* jackpatch.c

  This program is just like ASSPatch, except that it works with Jack ports (audio and MIDI).

 */

#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-result"

/* needed for asprintf */
#define _GNU_SOURCE

#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/time.h>
#include <errno.h>
#include <stdlib.h>
#include <jack/jack.h>
#include <getopt.h>

#include <lo/lo.h>

#include <jack/ringbuffer.h>

int client_active = 0;

jack_client_t *client;

lo_server losrv;
lo_address nsm_addr;
int nsm_is_active;

char *project_file;

int REAL_JACK_PORT_NAME_SIZE; //defined after jack client activated

#undef VERSION
#define APP_TITLE "JACKPatch"
#define VERSION "1.0.0"

struct patch_record {
    struct {
        char *client;
        char *port;
    } src , dst;
    int active;                                                 /* true if patch has already been activated (by us) */
    struct patch_record *next;
};


struct port_record {
    char *port;
    struct port_record *next;
};

struct port_notification_record {
    int len;
    int reg;                                                    /* true if registered, false if unregistered */
    char port[];
};

static struct port_record *known_ports = NULL;

static struct patch_record *patch_list = NULL;

static jack_ringbuffer_t *port_ringbuffer = NULL;

/**
 * Pretty-print patch relationship of /pr/
 */
void
print_patch ( struct patch_record *pr, int mode )
{
    printf( "[jackpatch] %s from '%s:%s' to '%s:%s'\n", mode ? ">>" : "::",
            pr->src.client, pr->src.port, pr->dst.client, pr->dst.port );

}

void
enqueue ( struct patch_record *p )
{
    p->next = patch_list;
    patch_list = p;
}

void
dequeue ( struct patch_record *pr )
{
    if ( !pr )
        return;

    free( pr->src.port );
    free( pr->dst.port );
    free( pr->src.client );
    free( pr->dst.client );

    free( pr );
}

void
enqueue_port ( struct port_record **q, const char *port )
{
    struct port_record *p = malloc( sizeof( struct port_record ));

    p->port = strdup( port );
    p->next = *q;
    *q = p;
}

void enqueue_known_port ( const char *port )
{
    enqueue_port( &known_ports, port );
}

/**
 * Find a jack port in our own data structure (and not in the jack graph)
 */
const char * find_known_port ( const char *port )
{
    struct port_record *pr;

    for ( pr = known_ports; pr; pr = pr->next )
        if ( !strcmp( port, pr->port ) )
            return pr->port;

    return NULL;
}


/**
 * Convert a symbolic string of a jack connection into actual data struct patch_record
 * Argument example:
 * "PulseAudio JACK Sink:front-left          |> system:playback_1"
 */
int
process_patch ( const char *patch )
{
    struct patch_record *pr;
    char *leftc, *rightc, *leftp, *rightp;
    char dir[3];

    int retval;

    retval = sscanf( patch, " %m[^:]:%m[^|] |%1[<>|] %m[^:]:%m[^\n]",
                     &leftc, &leftp, dir, &rightc, &rightp );

    if ( retval == EOF )
        return -1;

    if ( retval != 5 )
        return 0;

    /* trim space */
    int j;
    for ( j = strlen( leftp ) - 1; j > 0; j-- )
    {
        if ( leftp[j] == ' ' || leftp[j] == '\t' )
            leftp[j] = 0;
        else
            break;
    }

    dir[2] = 0;

    pr = malloc( sizeof( struct patch_record ) );

    switch ( *dir )
    {
        case '<':
            pr->src.client = rightc;
            pr->src.port = rightp;

            pr->dst.client = leftc;
            pr->dst.port = leftp;

            enqueue( pr );
            break;
        case '>':
            pr->src.client = leftc;
            pr->src.port = leftp;

            pr->dst.client = rightc;
            pr->dst.port = rightp;

            enqueue( pr );
            break;
        case '|':
            pr->src.client = rightc;
            pr->src.port = rightp;

            pr->dst.client = leftc;
            pr->dst.port = leftp;

            enqueue( pr );

            pr = malloc( sizeof( struct patch_record ) );

            pr->src.client = strdup( leftc );
            pr->src.port = strdup( leftp );

            pr->dst.client = strdup( rightc );
            pr->dst.port = strdup( rightp );

            enqueue( pr );
            break;
        default:
//            fprintf( stderr, "[jackpatch]  Invalid token '|%s' at line %i of %s!",  dir, i, file );
            free( pr );
            return 0;
    }

    pr->active = 0;

    //print_patch( pr, 1 ); //very verbose

    return 1;
}

void
clear_all_patches ( )
{
    struct patch_record *pr;

    while ( patch_list )
    {
        pr = patch_list;
        patch_list = pr->next;
        dequeue( pr );
    }
}

/**
 * Crudely parse configuration file named by /file/ using fscanf
 */
int
read_config ( const char *file )
{
    printf( "[jackpatch] Reading connections from file %s \n", file);
    FILE *fp;
    int i = 0;

    if ( NULL == ( fp = fopen( file, "r" ) ) )
         return 0;

    clear_all_patches();

    while ( !feof( fp ) && !ferror( fp ) )
    {
        int retval;
        unsigned int k;
        char buf[BUFSIZ];

        i++;

        for ( k = 0; k < sizeof( buf ) - 1; k++ )
        {
            retval = fread( buf + k, 1, 1, fp );
            if ( retval != 1 )
                break;
            if ( buf[k] == '\n' )
            {
                if ( k == 0 )
                    continue;
                else
                    break;
            }
        }

        if ( retval == 0 )
            break;

        retval = process_patch( buf );

        if ( retval < 0 )
            break;

        if ( retval == 0 )
        {
            printf( "[jackpatch] bad line %i.\n", i );
            continue;
        }
    }

    fclose( fp );

    return 1;
}



/** A connection attempt will only be made when a JACK port registers itself and we receive
 * the jack callback, and once on startup.
 * There is no periodic check if a previously saved connection is still alive (by design!).
 *
 * returns 0 if connection failed, true if succeeded. Already connected
 * is not considered failure */
void
connect_path ( struct patch_record *pr )
{
    int r = 0;

    char srcport[512]; // This should really be REAL_JACK_PORT_NAME_SIZE, but in the real world not every system and compiler does C99.
    char dstport[512];

    snprintf( srcport, REAL_JACK_PORT_NAME_SIZE, "%s:%s", pr->src.client, pr->src.port );
    snprintf( dstport, REAL_JACK_PORT_NAME_SIZE, "%s:%s", pr->dst.client, pr->dst.port );

    if ( pr->active )
    {
        /* patch is already active, don't bother JACK with it... */
        return;
    }

    if ( ! ( find_known_port( srcport ) && find_known_port( dstport )  ) )
    {

         /*
         * Since we only connect KNOWN TO US ports a connection will not be made on startup / file load,
         * even if both jack ports are actually present in JACK.
         * This is because we have not parsed both ports yet.
         * The true connection attempt will be made only when the second port of the pair was parsed.
         *
         * The log message below is misleading for users, because nothing is wrong, and should only
         * be used during development.
         *
         * We just skip the first attempt, eventhough JACK will not complain and do nothing wrong.
         *
         * That also means that we do not detect actually missing ports.
         */
        //printf( "[jackpatch] Not attempting connection because one of the ports is missing: %s %s\n", srcport, dstport );
        return;
    }

    // printf( "[jackpatch] Connecting %s |> %s\n", srcport, dstport );

    r = jack_connect( client, srcport, dstport );

    //print_patch( pr, r ); //very verbose

    if ( r == 0 || r == EEXIST )
    {
        pr->active = 1;
        return;
    }
    else
    {
        pr->active = 0;
        printf( "[jackpatch] Error is %i\n", r );
        return;
    }
}


void
do_for_matching_patches ( const char *portname, void (*func)( struct patch_record * ) )
{
    struct patch_record *pr;

    char client[512]; //Linux jack limit is 64
    char port[512];  //linux jack limit is 256

    sscanf( portname, "%[^:]:%[^\n]", client, port );

    for ( pr = patch_list; pr; pr = pr->next )
    {
        if ( ( !strcmp( client, pr->src.client ) && !strcmp( port, pr->src.port ) ) ||
             ( !strcmp( client, pr->dst.client ) && !strcmp( port, pr->dst.port ) ) )
        {
            func( pr );
        }
    }
}

void
inactivate_path ( struct patch_record *pr )
{
    pr->active = 0;
}

void
inactivate_patch ( const char *portname )
{
    do_for_matching_patches( portname, inactivate_path );
}

void
activate_patch ( const char *portname )
{
    do_for_matching_patches( portname, connect_path );
}

void remove_known_port ( const char *port )
{
    /* remove it from the list of known ports */
    {
        struct port_record *pr;
        struct port_record *lp = NULL;

        for ( pr = known_ports; pr; lp = pr, pr = pr->next )
            if ( !strcmp( port, pr->port ) )
            {
                if ( lp )
                    lp->next = pr->next;
                else
                    known_ports = pr->next;

                free( pr->port );
                free( pr );

                break;
            }
    }

    /* now mark all patches including this port as inactive */
    inactivate_patch ( port );
}

/**
 * Called for every new port, which includes restored-from-file ports on startup.
 * It will try to activate a restored connection for every single port, thus doing an attempt
 * twice: Once for the source-port and then for the destination-port.
 */
void
handle_new_port ( const char *portname )
{
    enqueue_known_port( portname );

    //Verbose output that a new port was detected during runtime
    //printf( "[jackpatch] New endpoint '%s' registered.\n", portname );

    /* this is a new port */
    activate_patch( portname );
}

void
register_prexisting_ports ( void )
{
    const char **port;
    const char **ports = jack_get_ports( client, NULL, NULL, 0 );

    if ( ! ports )
        return;

    for ( port = ports; *port; port++ )
    {
        handle_new_port( *port );
    }

    free( ports );
}

static int stringsort ( const void *a, const void *b )
{
    return strcmp(* (char * const *) a, * (char * const *) b);
}

/**
 * Save all current connections to a file.
 *
 * Strategy:
 * If there are no jack ports at all don't do anything. Else:
 *
 * Remember all currently known connections where one, or both, ports are missing from the jack graph.
 * We consider these temporarily gone by accident.
 *
 * Clear the current save file.
 *
 * For each currently existing jack output port save all of it's connections.
 * Save all these port-pairs in an empty file. Ports without connections are not saved.
 **
 */

void
snapshot ( const char *file )
{
    FILE *fp;

    const char **port;
    const char **ports = jack_get_ports( client, NULL, NULL,  JackPortIsOutput );

    if ( ! ports )
        return;

    if ( NULL == ( fp = fopen( file, "w" ) ) )
    {
        fprintf( stderr, "[jackpatch] Error opening snapshot file for writing\n" );
        return;
    }


    //Prepare a temporary table where all connection strings are held until the file is written at the bottom of this function.
    //We first add all connections that are temporarily out of order (see below) and then all currently existing connections.
    const int table_increment = 16;
    int table_index = 0;
    size_t table_size = table_increment;
    char **table = (char**)malloc( sizeof( char * ) * table_increment );


    //Before we forget the current state find all connections that we have in memory but where
    //one or both ports are currently missing in the jack graph.
    //We don't want to lose connections that are just temporarily not present.
    struct patch_record *pr;
    while ( patch_list )
    {
        //A patch is one connection between a source and a destination.
        //If an actual jack port source is connected to more than one destinations it will appear as it's own "patch" in this list.
        //We only need to consider 1:1 point connections in this loop.

        //Traverse the linked list
        pr = patch_list;
        patch_list = pr->next;

        int remember_this_connection = 0;

        char * src_client_port;
        char * dst_client_port;
        asprintf( &src_client_port, "%s:%s", pr->src.client, pr->src.port );
        asprintf( &dst_client_port, "%s:%s", pr->dst.client, pr->dst.port );

        jack_port_t *jp_t_src;
        jp_t_src = jack_port_by_name( client, src_client_port ); //client is our own jackpatch-jack-client.

        if ( ! jp_t_src ) {
            //The port does not exist anymore. We need to remember it!
            //It doesn't matter if the destination port still exists, the file-writing below will only consider ports that are currently present and connected.
            //printf("[jackpatch] We remember source %s but it does not exist anymore. Making sure it will not be forgotten.\n", src_client_port);
            remember_this_connection = 1;
        }
        else {
            //The source port does still exist, but is it's connection still alive?
            //Do not use jack_port_get_all_connections, we want to know if a specific destination is still there.
            jack_port_t *jp_t_dst;
            jp_t_dst = jack_port_by_name( client, dst_client_port ); //client is our own jackpatch-jack-client.
             if ( ! jp_t_dst ) {
                //The port does not exist anymore. We need to remember it!
                //It doesn't matter if the destination port still exists, the file-writing below will only consider ports that are currently present and connected.
                //printf("[jackpatch] We remember destination %s but it does not exist anymore. Making sure it will not be forgotten.\n", dst_client_port);
                remember_this_connection = 1;
            }
        }
        if ( remember_this_connection ) {
            //const char * pport = src_client_port;
            //const char * pclient = dst_client_port;

            //This code is replicated below #TODO: create function.
            char *s;
            asprintf( &s, "%-40s |> %s\n", src_client_port, dst_client_port ); //prepare the magic string that is the step before creating a struct from with process_patch //port is source client:port and connection is the destination one.
            if ( table_index >= table_size )
            {
                table_size += table_increment;
                table = (char**)realloc( table, table_size * sizeof( char *) );
            }
            table[table_index++] = s;
            // process_patch( s ); infinite loop! But we still need to keep these patch_records! See below
            // Verbose output that an individual connection was saved.
            //printf( "[jackpatch] Remember ++ %s |> %s\n", src_client_port, dst_client_port );
        }
        free ( src_client_port );
        free ( dst_client_port );
    }

    clear_all_patches(); //Tabula Rasa.

    //We just removed the patch_records we wanted to remember.
    //The last table_index holds the number of remembered records.
    for ( int record=0; record < table_index; record++ )
    {
        process_patch ( table[record] );
    }

    for ( port = ports; *port; port++ )
    {
        //*port is a full client:port jack name, not only the port.
        jack_port_t *p;

        p = jack_port_by_name( client, *port );

        const char **connections;
        const char **connection;

        connections = jack_port_get_all_connections( client, p );

        if ( ! connections )
            continue;

        for ( connection = connections; *connection; connection++ )
        {
            //This code is replicated above #TODO: create function.
            char *s;
            asprintf( &s, "%-40s |> %s\n", *port, *connection ); //prepare the magic string that is the step before creating a struct from with process_patch //port is source client:port and connection is the destination one.
            if ( table_index >= table_size )
            {
                table_size += table_increment;
                table = (char**)realloc( table, table_size * sizeof( char *) );
            }
            table[table_index++] = s;
            process_patch( s );
            // Verbose output that an individual connection was saved.
            //printf( "[jackpatch]  ++ %s |> %s\n", *port, *connection );
        }

        free( connections );
    }

    free( ports );

    qsort( table, table_index, sizeof(char*), stringsort );

    int i;
    for ( i = 0; i < table_index; i++ )
    {
        fprintf( fp, "%s", table[i] );
        free(table[i]);
    }

    free(table);

    fclose( fp );

}

static int die_now = 0;

void
signal_handler ( int x )
{
    printf("[jackpatch] Handle signal %d\n", x);
    die_now = 1;
}

void
die ( void )
{
    if ( client_active )
        jack_deactivate( client );
    printf( "[jackpatch] Closing jack client\n" );

    jack_client_close( client );
    client = NULL;
    exit( 0 );
}

/** set_traps
 *
 * Handle signals
 */
void
set_traps ( void )
{
    signal( SIGHUP, signal_handler );
    signal( SIGINT, signal_handler );
//  signal( SIGQUIT, signal_handler );
//  signal( SIGSEGV, signal_handler );
//  signal( SIGPIPE, signal_handler );
    signal( SIGTERM, signal_handler );
}

/****************/
/* OSC HANDLERS */
/****************/

int
osc_announce_error ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    if ( strcmp( types, "sis" ) )
        return -1;

    if ( strcmp( "/nsm/server/announce", &argv[0]->s ) )
         return -1;

    printf( "[jackpatch] Failed to register with NSM: %s\n", &argv[2]->s );
    nsm_is_active = 0;

    return 0;
}


int
osc_announce_reply ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    if ( strcmp( "/nsm/server/announce", &argv[0]->s ) )
         return -1;

    printf( "[jackpatch] Successfully registered. NSM says: %s\n", &argv[1]->s );

    nsm_is_active = 1;
    nsm_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ) );

    return 0;
}

int
osc_save ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    snapshot( project_file );

    lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );

    return 0;
}

void
maybe_activate_jack_client ( void )
{
    if ( ! client_active )
    {
        jack_activate( client );
        client_active = 1;
        REAL_JACK_PORT_NAME_SIZE = jack_port_name_size(); //global. This is client+port+1. 64 + 256 + 1 = 321 on Linux.
    }
}

int
osc_open ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    const char *new_path = &argv[0]->s;
//    const char *display_name = &argv[1]->s;

    char *new_filename;

    asprintf( &new_filename, "%s.jackpatch", new_path );

    struct stat st;

    if ( 0 == stat( new_filename, &st ) )
    {
        if ( read_config( new_filename ) )
        {
            /* wipe_ports(); */
            /* check_for_new_ports(); */
            maybe_activate_jack_client();
            register_prexisting_ports();
        }
        else
        {
            lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/error", "sis", path, -1, "Could not open file" );
            return 0;
        }
    }
    else
    {
        maybe_activate_jack_client();
        clear_all_patches();
    }

    if ( project_file )
        free( project_file );

    project_file = new_filename;

    lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );

    return 0;
}

void
announce ( const char *nsm_url, const char *client_name, const char *process_name )
{
    printf( "[jackpatch] Announcing to NSM\n" );

    lo_address to = lo_address_new_from_url( nsm_url );

    int pid = (int)getpid();

    lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/server/announce", "sssiii",
                  client_name,
                  ":switch:",
                  process_name,
                  0, /* api_major_version */
                  8, /* api_minor_version */
                  pid );

    lo_address_free( to );
}

void
init_osc ( const char *osc_port )
{
    losrv = lo_server_new( osc_port, NULL );
//error_handler );

    char *url = lo_server_get_url(losrv);
    printf("[jackpatch] OSC: %s\n",url);
    free(url);

    lo_server_add_method( losrv, "/nsm/client/save", "", osc_save, NULL );
    lo_server_add_method( losrv, "/nsm/client/open", "sss", osc_open, NULL );
    lo_server_add_method( losrv, "/error", "sis", osc_announce_error, NULL );
    lo_server_add_method( losrv, "/reply", "ssss", osc_announce_reply, NULL );
}

struct port_notification_record *
dequeue_new_port ( void )
{
    int size = 0;

    if ( sizeof( int ) == jack_ringbuffer_peek( port_ringbuffer, (char*)&size, sizeof( int ) ) )
    {
        if ( jack_ringbuffer_read_space( port_ringbuffer ) >= size )
        {
            struct port_notification_record *pr = malloc( size );

            jack_ringbuffer_read( port_ringbuffer, (char*)pr, size );

            return pr;
        }
    }

    return NULL;
}


void
check_for_new_ports ( void )
{
    struct port_notification_record *p = NULL;

    while ( ( p = dequeue_new_port() ) )
    {
        if ( p->reg )
            handle_new_port( p->port );
        else
            remove_known_port( p->port );

        free( p );
    }
}

void
port_registration_callback( jack_port_id_t id, int reg, void *arg )
{
    jack_port_t *p = jack_port_by_id( client, id );
    const char *port = jack_port_name( p );

    int size = strlen(port) + 1 + sizeof( struct port_notification_record );

    struct port_notification_record *pr = malloc( size );

    pr->len = size;
    pr->reg = reg;
    strcpy( pr->port, port );

    if ( size != jack_ringbuffer_write( port_ringbuffer, (const char *)pr, size ) )
    {
        fprintf( stderr, "[jackpatch] ERROR: port notification buffer overrun\n" );
    }

//    enqueue_new_port( port, reg );
}

/*  */

int
main ( int argc, char **argv )
{


    //Command line parameters
    static struct option long_options[] =
    {
        { "help", no_argument, 0, 'h' },
        { "save", no_argument, 0, 's' },
        { "version", no_argument, 0, 'v' },
        { 0, 0, 0, 0 }
    };
    int option_index = 0;
    int c = 0;
    while ( ( c = getopt_long_only( argc, argv, "", long_options, &option_index  ) ) != -1 )
    {
        switch ( c )
        {
            case 'h':
                {
                const char *usage =
                "jackpatch - Remember and restore the JACK Audio Connection Kit Graph in NSM\n\n"
                "It is intended as module for the 'New Session Manager' and only communicates\n"
                "over OSC in an NSM-Session.\n\n"
                "It has limited standalone functionality for testing and debugging, such as:\n"
                "\n"
                "Usage:\n"
                "  jackpatch [filename]  Restore a snapshot from --save and monitor.\n"
                "  jackpatch --help\n"
                "\n"
                "Options:\n"
                "  --help                Show this screen and exit\n"
                "  --version             Show version and exit\n"
                "  --save                Save current connection snapshot to file and exit\n"
                "";
                puts ( usage );
                exit(0);
                break;
                }

           case 's':
                {
                    //save is handled below.
                    break;
                }

           case 'v':
                {
                    printf("%s\n", VERSION);
                    exit(0);
                    break;
                }
        }
    }


    jack_status_t status;

    client = jack_client_open( APP_TITLE, JackNullOption, &status );

    jack_set_port_registration_callback( client, port_registration_callback, NULL );

    if ( ! client )
    {
        fprintf( stderr, "[jackpatch] Could not register JACK client\n" );
        exit(1);

    }

    port_ringbuffer = jack_ringbuffer_create( 1024 * 8 );

    set_traps();

    if ( argc > 1 )
    {
        maybe_activate_jack_client();
        if ( ! strcmp( argv[1], "--save" ) )
        {
            if ( argc > 2 )
            {

                //To not discard temporarily missing clients we need to load the current ones from file first.
                if ( read_config( argv[2] ) ) // --save parameter
                {
                    register_prexisting_ports();
                }

                printf( "[jackpatch] Standalone: Saving current graph to: %s\n", argv[2] );
                snapshot( argv[2] );
                die();
            }
        }
        else
        {
            /**
             * Enter standalone commandline mode. This is without NSM.
             */
            if ( read_config( argv[1] ) )
            {
                maybe_activate_jack_client();
                register_prexisting_ports();
            }

            printf( "[jackpatch] Monitoring in standalone mode…\n" );
            for ( ;; )
            {
                usleep( 50000 );
                if ( die_now )
                    die();
                check_for_new_ports();
            }
        }
    }

    init_osc( NULL );

    const char *nsm_url = getenv( "NSM_URL" );

    if ( nsm_url )
    {
        announce( nsm_url, APP_TITLE, argv[0] );
    }
    else
    {
        fprintf( stderr, "[jackpatch] Could not register as NSM client.\n" );
        exit(1);
    }

    for ( ;; )
    {
        lo_server_recv_noblock( losrv, 200 );

        if ( client_active )
            check_for_new_ports();

        if ( die_now )
            die();
    }
}
07070100000041000081A40000000000000000000000016258A65C00002616000000000000000000000000000000000000004100000000new-session-manager-1.6.0+git.20220415.0f6719c/src/jackpatch.svg<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="68.791656mm"
   height="68.791656mm"
   viewBox="0 0 68.791656 68.791656"
   version="1.1"
   id="svg8"
   sodipodi:docname="icons.svg"
   inkscape:version="1.0 (4035a4fb49, 2020-05-01)">
  <defs
     id="defs2" />
  <sodipodi:namedview
     fit-margin-bottom="0"
     fit-margin-right="0"
     fit-margin-left="0"
     fit-margin-top="0"
     units="px"
     id="base"
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0.0"
     inkscape:pageshadow="2"
     inkscape:zoom="0.98994949"
     inkscape:cx="-402.6148"
     inkscape:cy="-222.59193"
     inkscape:document-units="mm"
     inkscape:current-layer="layer1"
     inkscape:document-rotation="0"
     showgrid="false"
     inkscape:window-width="2558"
     inkscape:window-height="1398"
     inkscape:window-x="0"
     inkscape:window-y="20"
     inkscape:window-maximized="1" />
  <metadata
     id="metadata5">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     transform="translate(-230.22857,-195.0233)"
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1">
    <g
       id="nsm"
       inkscape:label="nsm"
       transform="translate(-172.56864)">
      <title
         id="title888">nsm</title>
      <rect
         y="114.63333"
         x="71.133331"
         height="67.73333"
         width="67.73333"
         id="rect10"
         style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
      <text
         xml:space="preserve"
         style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
         x="105.32493"
         y="169.25755"
         id="text852"><tspan
           sodipodi:role="line"
           x="105.32493"
           y="169.25755"
           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
           id="tspan850">NSM</tspan></text>
    </g>
    <g
       transform="translate(-170.13191,80.919145)"
       inkscape:label="jackpatch"
       id="jackpatch">
      <title
         id="title890">jackpatch</title>
      <rect
         style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
         id="rect892"
         width="67.73333"
         height="67.73333"
         x="71.133331"
         y="114.63333" />
      <text
         id="text896"
         y="165.8237"
         x="106.38719"
         style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
         xml:space="preserve"><tspan
           id="tspan894"
           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
           y="165.8237"
           x="106.38719"
           sodipodi:role="line">JP</tspan></text>
    </g>
    <g
       id="proxy"
       inkscape:label="proxy">
      <title
         id="title940">proxy</title>
      <rect
         y="37.223072"
         x="-99.065941"
         height="67.73333"
         width="67.73333"
         id="rect902"
         style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
      <text
         xml:space="preserve"
         style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
         x="-65.639175"
         y="93.759422"
         id="text906"><tspan
           sodipodi:role="line"
           x="-65.639175"
           y="93.759422"
           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
           id="tspan904">PRX</tspan></text>
    </g>
    <path
       d="m 231.64549,37.223072 h 67.73333 V 104.9564 h -67.73333 z"
       style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
       id="rect1679" />
    <g
       style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
       id="text1683"
       aria-label="PRX">
      <path
         id="path1690"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 238.98532,48.420053 0.0677,45.339369 h 4.87229 V 72.578493 l 0.27069,-0.06767 h 4.60161 c 1.42108,0 2.63915,-0.406024 3.65421,-1.150401 1.01506,-0.744378 1.48876,-1.962451 1.48876,-3.654218 V 53.427685 c 0,-1.96245 -0.4737,-3.315864 -1.42109,-3.992571 -0.94739,-0.676707 -2.16546,-1.015061 -3.72188,-1.015061 z m 4.93996,4.872291 h 3.31587 c 0.47369,0 0.81205,0.135341 1.1504,0.473695 0.33835,0.338353 0.4737,0.744377 0.4737,1.218072 v 10.962654 c 0,0.473694 -0.13535,0.812048 -0.4737,1.150401 -0.33835,0.338354 -0.67671,0.473695 -1.1504,0.473695 h -3.31587 z" />
      <path
         id="path1692"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 262.66997,53.292344 h 3.31586 c 0.4737,0 0.81205,0.135341 1.15041,0.473695 0.33835,0.338353 0.47369,0.744377 0.47369,1.218072 v 10.962654 c 0,0.473694 -0.13534,0.812048 -0.47369,1.150401 -0.33836,0.338354 -0.67671,0.473695 -1.15041,0.473695 h -3.31586 z m -4.93996,-4.872291 0.0677,45.339369 H 262.67 v -21.2486 h 1.21807 l 4.19559,21.2486 h 4.87229 L 268.69266,72.24014 c 0.60904,-0.203012 1.42109,-0.609037 2.50382,-1.218073 0.94739,-0.609036 1.48875,-1.759438 1.48875,-3.451206 V 53.427685 c 0,-1.285743 -0.47369,-2.436145 -1.48875,-3.518876 -1.01506,-1.015061 -2.16547,-1.488756 -3.51888,-1.488756 z" />
      <path
         id="path1694"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 275.6627,48.420053 5.0753,23.346392 -4.80462,21.789965 h 5.00763 l 2.90984,-13.53414 2.90984,13.53414 h 4.93996 l -4.73695,-21.789965 5.07531,-23.346392 h -5.14298 l -3.04518,14.752213 -3.04518,-14.752213 z" />
    </g>
    <path
       d="m 230.75774,195.55247 h 67.73333 v 67.73333 h -67.73333 z"
       style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
       id="rect1700" />
    <g
       style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
       id="text1704"
       aria-label="JP">
      <path
         id="path1712"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 259.34614,252.35951 v -50.95604 h -5.0753 v 49.46729 c 0,0.47369 -0.13534,0.81204 -0.4737,1.1504 -0.33835,0.33835 -0.6767,0.47369 -1.1504,0.47369 h -1.75944 v 4.93996 h 3.04518 c 3.58655,0 5.41366,-1.69176 5.41366,-5.0753 z" />
      <path
         id="path1714"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 263.40627,201.40347 0.0677,45.33937 h 4.87229 v -21.18093 l 0.27068,-0.0677 h 4.60161 c 1.42108,0 2.63916,-0.40602 3.65422,-1.1504 1.01506,-0.74438 1.48875,-1.96245 1.48875,-3.65422 v -14.27851 c 0,-1.96245 -0.47369,-3.31587 -1.42108,-3.99257 -0.94739,-0.67671 -2.16546,-1.01507 -3.72189,-1.01507 z m 4.93996,4.87229 h 3.31586 c 0.4737,0 0.81205,0.13535 1.1504,0.4737 0.33836,0.33835 0.4737,0.74438 0.4737,1.21807 v 10.96266 c 0,0.47369 -0.13534,0.81204 -0.4737,1.1504 -0.33835,0.33835 -0.6767,0.47369 -1.1504,0.47369 h -3.31586 z" />
    </g>
  </g>
</svg>
07070100000042000081A40000000000000000000000016258A65C0000ABA9000000000000000000000000000000000000004600000000new-session-manager-1.6.0+git.20220415.0f6719c/src/nsm-legacy-gui.cpp
/*******************************************************************************/
/* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager")     */
/* Copyright (C) 2020- Nils Hilbricht                                          */
/*                                                                             */
/* This file is part of New-Session-Manager                                    */
/*                                                                             */
/* New-Session-Manager 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.                                         */
/*                                                                             */
/* New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
/*******************************************************************************/



#include "Endpoint.hpp"


#include <FL/Fl.H>

#include <FL/Fl_Window.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Widget.H>
#include <FL/Fl.H>
#include <FL/Fl_File_Chooser.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Pack.H>
#include <FL/Fl_File_Chooser.H>
#include <FL/Fl_Progress.H>
#include "debug.h"
#include <FL/Fl_Browser.H>
#include <FL/Fl_Select_Browser.H>
#include <FL/Fl_Tree.H>
#include <FL/Fl_Hold_Browser.H>
#include <FL/Fl_Tile.H>
#include <FL/Fl_Shared_Image.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Text_Display.H>
#include "FL/Fl_Packscroller.H"
#include "FL/Fl_Scalepack.H"

#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <getopt.h>

#define APP_NAME "NSM Legacy GUI"
#define APP_TITLE "NSM Legacy GUI"

#pragma GCC diagnostic ignored "-Wunused-result"

// static lo_address nsm_addr = NULL;
static time_t last_ping_response;

static OSC::Endpoint *osc;

struct Daemon
{
    const char *url;
    lo_address addr;
    bool is_child;

    Daemon ( )
        {
            url = NULL;
            addr = NULL;
            is_child = false;
        }
};

static std::list<Daemon*> daemon_list;                                  /* list of all connected daemons */

#define foreach_daemon( _it ) for ( std::list<Daemon*>::iterator _it = daemon_list.begin(); _it != daemon_list.end(); ++ _it )

static
Fl_Image *
get_program_icon ( const char *name )
{
    const char *tries[] =
    {
        "/usr/local/share/icons/hicolor/32x32/apps/%s.png",
        "/usr/local/share/pixmaps/%s.png",
        "/usr/local/share/pixmaps/%s.xpm",
        "/usr/share/icons/hicolor/32x32/apps/%s.png",
        "/usr/share/icons/hicolor/128x128/apps/%s.png",
        "/usr/share/pixmaps/%s.png",
        "/usr/share/pixmaps/%s.xpm",
    };

    for ( unsigned int i = 0; i < 6; i++ )
    {
        char *icon_p;

        asprintf( &icon_p, tries[i], name );

        Fl_Image *img = Fl_Shared_Image::get( icon_p, 32, 32 );

        free( icon_p );

        if ( img )
            return img;
    }

    return NULL;
}

class NSM_Client : public Fl_Group
{
    char *_client_id;
    char *_client_label;
    char *_client_name;

    Fl_Box *client_name;
    Fl_Box *icon_box;
    Fl_Progress *_progress;
    Fl_Light_Button *_dirty;
    Fl_Light_Button *_gui;
    Fl_Button *_remove_button;
    Fl_Button *_restart_button;
    Fl_Button *_kill_button;

    void
    set_label ( void )
        {
            char *l;

            if ( _client_label && _client_label[0] != '\0' )
                asprintf( &l, "%s (%s)", _client_name, _client_label );
            else
                l = strdup( _client_name );

            if ( ! icon_box->image() )
            {
                Fl_Image *img = get_program_icon( _client_name );

                if ( img )
                {
                    icon_box->image( img );
                }
            }

            client_name->copy_label( l );

            free(l);

            redraw();
        }

public:

    void
    name ( const char *v )
        {
            if ( _client_name )
                free( _client_name );

            _client_name = strdup( v );

            set_label();
        }

    void
    client_label ( const char *s )
        {
            if ( _client_label )
                free( _client_label );

            _client_label = strdup( s );

            set_label();
        }

    void
    client_id ( const char *v )
        {
            if ( _client_id )
                free( _client_id );

            _client_id = strdup( v );
        }

    void
    progress ( float f )
        {
            _progress->value( f );
            _progress->redraw();
        }

    void
    dirty ( bool b )
        {
            _dirty->value( b );
            _dirty->redraw();
        }

    void
    gui_visible ( bool b )
        {
            _gui->value( b );
            _gui->redraw();
        }


    void
    has_optional_gui ( void )
        {
            _gui->show();
            _gui->redraw();
        }

    void
    stopped ( bool b )
        {
            if ( b )
            {
                _remove_button->show();
                _restart_button->show();
                _kill_button->hide();
                _gui->deactivate();
                _dirty->deactivate();
                redraw();
            }
            else
            {
                _gui->activate();
                _dirty->activate();
                _kill_button->show();
                _restart_button->hide();
                _remove_button->hide();
            }

            /* _restart_button->redraw(); */
            /* _remove_button->redraw(); */
        }

    void
    pending_command ( const char *command )
        {
            _progress->copy_label( command );

            stopped( 0 );

            if ( ! strcmp( command, "ready" ) )
            {
                _progress->value( 0.0f );
            }
            else if ( ! strcmp( command, "quit" ) ||
                      ! strcmp( command, "kill" ) ||
                      ! strcmp( command, "error" ) )
            {
                //Set a border color to indicate warning
                color( fl_color_average( FL_BLACK, FL_RED, 0.50 ) );
            }
            else if ( ! strcmp( command, "stopped" ) )
            {
                stopped( 1 );
            }

            redraw();
        }


    static void
    cb_button ( Fl_Widget *o, void * v )
        {
            ((NSM_Client*)v)->cb_button( o );
        }

    void
    cb_button ( Fl_Widget *o )
        {
            if ( o == _dirty )
            {
                MESSAGE( "Sending save.");
                foreach_daemon ( d )
                {
                    osc->send( (*d)->addr, "/nsm/gui/client/save", _client_id );
                }
            }
            else if ( o == _gui )
            {
                MESSAGE( "Sending hide/show GUI.");
                foreach_daemon ( d )
                {
                    if ( !_gui->value() )
                        osc->send( (*d)->addr, "/nsm/gui/client/show_optional_gui", _client_id );
                    else
                        osc->send( (*d)->addr, "/nsm/gui/client/hide_optional_gui", _client_id );
                }
            }
            else if ( o == _remove_button )
            {
                MESSAGE( "Sending remove.");
                foreach_daemon ( d )
                {
                    osc->send( (*d)->addr, "/nsm/gui/client/remove", _client_id );
                }
            }
            else if ( o == _restart_button )
            {
                MESSAGE( "Sending resume" );
                foreach_daemon ( d )
                {
                    osc->send( (*d)->addr, "/nsm/gui/client/resume", _client_id );
                }
            }
            else if ( o == _kill_button )
            {
                MESSAGE( "Sending stop" );
                foreach_daemon ( d )
                {
                    osc->send( (*d)->addr, "/nsm/gui/client/stop", _client_id );
                }
            }
        }


    const char *
    client_id ( void )
        { return _client_id; }

    NSM_Client ( int X, int Y, int W, int H, const char *L ) :
        Fl_Group( X, Y, W, H, L )
        {

            _client_id = NULL;
            _client_name = NULL;
            _client_label = NULL;

            align( FL_ALIGN_LEFT | FL_ALIGN_INSIDE );
            box( FL_UP_FRAME );

            int yy = Y + H * 0.25;
            int hh = H * 0.50;
            int xx = X + W - ( 75 + Fl::box_dw( box() ) );
            int ss = 2;

            /* /\* dummy group *\/ */
            /* { Fl_Group *o = new Fl_Group( X, Y, W, H ); */
            /*     o->end(); */
            /*     resizable( o ); */
            /* } */

            { Fl_Pack *o = new Fl_Pack( X + 15, Y, 300 - 5, H );
                o->type( FL_HORIZONTAL );
                o->spacing( 10 );
                {  icon_box = new Fl_Box( 0, 0, 32, 32 );
                }

                { Fl_Box *o = client_name = new Fl_Box( 0, 0, 300, 48 );
                    o->align( FL_ALIGN_INSIDE | FL_ALIGN_LEFT );
                    o->labeltype( FL_NORMAL_LABEL );
                }
                o->end();
            }

            { Fl_Box *o = new Fl_Box( X + 300, Y, 100, h() );
                Fl_Group::current()->resizable(o);
                }

            { Fl_Progress *o = _progress = new Fl_Progress( xx, Y + H * 0.25, 75, H * 0.50, NULL );
                o->box( FL_FLAT_BOX );
                o->copy_label( "launch" );
                o->labelsize( 12 );
                o->minimum( 0.0f );
                o->maximum( 1.0f );
            }

            { Fl_Group *o = new Fl_Group( X + W - 400, Y, 400, H );

                xx -= 50 + ss;

                { Fl_Light_Button *o = _dirty = new Fl_Light_Button( xx, yy, 50, hh, "SAVE" );

                    o->align( FL_ALIGN_LEFT | FL_ALIGN_INSIDE );
                    o->labelsize( 9 );
                    o->box( FL_UP_BOX );
                    o->type(0);
                    o->value( 0 );
                    o->callback( cb_button, this );
                }

                xx -= 40 + ss;

                { Fl_Light_Button *o = _gui = new Fl_Light_Button( xx, yy, 40, hh, "GUI" );

                    o->align( FL_ALIGN_LEFT | FL_ALIGN_INSIDE );
                    o->labelsize( 9 );
                    o->box( FL_UP_BOX );
                    o->type(0);
                    o->value( 0 );
                    o->hide();
                    o->callback( cb_button, this );
                }

                xx -= 25 + ss;

                { Fl_Button *o = _kill_button = new Fl_Button( xx, yy, 25, hh, "@square" );
                    o->labelsize( 9 );
                    o->box( FL_UP_BOX );
                    o->type(0);
                    o->value( 0 );
                    o->tooltip( "Stop" );
                    o->callback( cb_button, this );
                }

                xx -= 25 + ss;

                { Fl_Button *o = _restart_button = new Fl_Button( xx, yy, 25, hh );


                    o->box( FL_UP_BOX );
                    o->type(0);
                    o->value( 0 );
                    o->label( "@>" );
                    o->tooltip( "Resume" );
                    o->hide();
                    o->callback( cb_button, this );
                }

                xx -= 25 + ss;

                { Fl_Button *o = _remove_button = new Fl_Button( xx, yy, 25, hh );


                    o->box( FL_UP_BOX );
                    o->type(0);
                    o->value( 0 );
                    o->label( "X" );
                    o->tooltip( "Remove" );
                    o->hide();
                    o->callback( cb_button, this );
                }


                o->end();
            }
            end();
        }

    ~NSM_Client ( )
        {
            if ( _client_id )
            {
                free( _client_id );
                _client_id = NULL;
            }

            if ( _client_name )
            {
                free( _client_name );
                _client_name = NULL;
            }

            if ( _client_label )
            {
                free( _client_label );
                _client_label = NULL;
            }

            if ( label() )
            {
                free( (char*)label() );
                label( NULL );
            }
        }
};

static
void
fl_awake_alert( void *v )
{
    if ( v )
    {
        fl_alert( "%s", (char*)v );
        free( v );
    }
}

void
browser_callback ( Fl_Widget *w, void * )
{
    w->window()->hide();
}

class NSM_Controller : public Fl_Group
{

    Fl_Text_Display *status_display;

public:

    Fl_Pack *clients_pack;
    Fl_Pack *buttons_pack;
    Fl_Button *close_button;
    Fl_Button *abort_button;
    Fl_Button *save_button;
    Fl_Button *open_button;
    Fl_Button *new_button;
    Fl_Button *add_button;
    Fl_Button *duplicate_button;
    Fl_Button *quit_button;
    Fl_Button *refresh_button;
    Fl_Box *session_name_box;

    Fl_Tree *session_browser;

    int status_lines;

    static void cb_handle ( Fl_Widget *w, void *v )
        {
            ((NSM_Controller*)v)->cb_handle( w );

        }

    void log_status ( const char *s )
        {
            time_t now;

            now = time( NULL );

            struct tm * tm =  localtime( &now );

            char *ts;
            asprintf( &ts, "%02i:%02i:%02i ", tm->tm_hour, tm->tm_min, tm->tm_sec );

            status_display->buffer()->append( ts );
            free( ts );

            status_display->buffer()->append( s );
            status_display->scroll( ++status_lines, 0 );
            status_display->buffer()->append( "\n" );
        }

    void
    cb_handle ( Fl_Widget *w )
        {
            if ( w == abort_button )
            {
                if ( 0 == fl_choice( "Are you sure you want to close this session? Unsaved changes will be lost.", "Close anyway", "Cancel", NULL ) )
                {
                    MESSAGE( "Sending abort." );

                    foreach_daemon ( d )
                    {
                        osc->send( (*d)->addr, "/nsm/server/abort" );
                    }
                }
            }
            if ( w == close_button )
            {
                MESSAGE( "Sending close." );
                foreach_daemon ( d )
                {
                    osc->send( (*d)->addr, "/nsm/server/close" );
                }
            }
            else if ( w == save_button )
            {
                MESSAGE( "Sending save." );
                foreach_daemon ( d )
                {
                    osc->send( (*d)->addr, "/nsm/server/save" );
                }
            }
            else if ( w == open_button )
            {
                const char *name = fl_input( "Open Session", NULL );

                if ( ! name || name[0] == '\0' )
                    return;

                Fl_Tree_Item *item = session_browser->find_item( name );

                if ( item )
                    session_browser->select_only( item, 1 );
            }
            else if ( w == duplicate_button )
            {
                const char *name = fl_input( "New Session", NULL );

                if ( ! name || name[0] == '\0' )
                    return;

                MESSAGE( "Sending duplicate for: %s", name );
                foreach_daemon ( d )
                {
                    osc->send( (*d)->addr, "/nsm/server/duplicate", name );
                }
            }
            else if ( w == quit_button )
            {
                window()->do_callback( window(), this );
            }
            else if ( w == refresh_button )
            {
                session_browser->clear();
                session_browser->redraw();
                MESSAGE( "Refreshing session list." );
                foreach_daemon ( d )
                {
                    osc->send( (*d)->addr, "/nsm/server/list" );
                }
            }
            else if ( w == session_browser )
            {
                if ( session_browser->callback_reason() != FL_TREE_REASON_SELECTED )
                    return;

                Fl_Tree_Item *item = session_browser->callback_item();

                if ( item )
                    session_browser->deselect( item, 0 ); //Deselect on program start, otherwise it looks like the first session is already loaded.

                if ( item->children() )
                    return;

                char name[1024];

                session_browser->item_pathname( name, sizeof(name), item );

                foreach_daemon ( d )
                {
                    osc->send( (*d)->addr, "/nsm/server/open", name );
                }
            }
            else if ( w == new_button )
            {
                const char *name = fl_input( "New Session", NULL );

                if ( ! name || name[0] == '\0' )
                    return;

                MESSAGE( "Sending new for: %s", name );
                foreach_daemon ( d )
                {
                    osc->send( (*d)->addr, "/nsm/server/new", name );
                }
            }
            else if ( w == add_button )
            {
                Fl_Select_Browser *browser;

                if ( daemon_list.size() > 1 )
                {
                    Fl_Window* win = new Fl_Window( window()->x(), window()->y(), 300, 400, "Choose Server" );
                    {
                        {
                            Fl_Box *o = new Fl_Box( 0,0, 300, 100 );

                            o->label( "Connected to multiple NSM servers, please select which one to add a client to." );
                            o->align( FL_ALIGN_CENTER | FL_ALIGN_INSIDE | FL_ALIGN_WRAP );
                        }
                        {
                            Fl_Select_Browser *o = browser = new Fl_Select_Browser( 0, 100, 300, 300 );
                            o->box( FL_ROUNDED_BOX );
                            o->callback( browser_callback, win );
                            foreach_daemon( d )
                            {
                                o->add( (*d)->url );
                            }
                        }
                    }

                    win->end();

                    win->show();

                    while ( win->visible() )
                    {
                        Fl::wait();
                    }

                    if ( ! browser->value() )
                        return;

                    const char *n = fl_input( "Enter executable name" );

                    if ( !n || !*n || n[0] == '\0' )
                        return;

                    char *name = strdup( n );

                    lo_address nsm_addr = lo_address_new_from_url( browser->text( browser->value() ) );

                    osc->send( nsm_addr, "/nsm/server/add", name );

                    free( name );

                    delete win;
                }
                else
                {
                    const char *n = fl_input( "Enter executable name" );

                    if ( !n || !*n || n[0] == '\0' )
                        return;

                    char *name = strdup( n );

                    MESSAGE( "Sending add for: %s", name );
                    /* FIXME: user should get to choose which system to do the add on */
                    foreach_daemon ( d )
                    {
                        osc->send( (*d)->addr, "/nsm/server/add", name );
                    }

                    free( name );
                }

            }
        }


    NSM_Client *
    client_by_id ( const char *id )
        {
            for ( int i = clients_pack->children(); i--; )
            {
                NSM_Client *c = (NSM_Client*)clients_pack->child( i );

                if ( ! strcmp( c->client_id(), id ) )
                {
                    return c;
                }
            }
            return NULL;
        }


    const char *session_name ( void ) const
        {
            return session_name_box->label();
        }

    void
    session_name ( const char *name )
        {
            session_name_box->copy_label( name );

            if ( strlen( name ) )
            {
                save_button->activate();
                add_button->activate();
                duplicate_button->activate();
                abort_button->activate();
                close_button->activate();
            }
            else
            {
                save_button->deactivate();
                add_button->deactivate();
                duplicate_button->deactivate();
                abort_button->deactivate();
                close_button->deactivate();
            }

            redraw();
        }

    void
    client_stopped ( const char *client_id )
        {
            NSM_Client *c = client_by_id( client_id );

            if ( c )
            {
                c->stopped( 1 );
            }
        }

    void
    client_quit ( const char *client_id )
        {
            NSM_Client *c = client_by_id( client_id );

            if ( c )
            {
                clients_pack->remove( c );
                delete c;
            }

            if ( clients_pack->children() == 0 )
            {
                ((Fl_Packscroller*)clients_pack->parent())->yposition( 0 );
            }

            parent()->redraw();
        }

    void
    client_new ( const char *client_id, const char *client_name )
        {

            NSM_Client *c;

            c = client_by_id( client_id );

            if ( c )
            {
                c->name( client_name );
                return;
            }

            c = new NSM_Client( 0, 0, w(), 40, NULL );

            c->name( client_name );
            c->client_id( client_id );
            c->stopped( 0 );

            clients_pack->add( c );

            redraw();
        }

    void client_pending_command ( NSM_Client *c, const char *command )
        {
            if ( c )
            {
                if ( ! strcmp( command, "removed" ) )
                {
                    clients_pack->remove( c );
                    delete c;

                    parent()->redraw();
                }
                else
                    c->pending_command( command );
            }
        }


    void add_session_to_list ( const char *name )
        {
            session_browser->add( name );
            session_browser->redraw();
        }


    NSM_Controller ( int X, int Y, int W, int H, const char *L ) :
        Fl_Group( X, Y, W, H, L )
        {
            status_lines = 0;

            align( FL_ALIGN_RIGHT | FL_ALIGN_CENTER | FL_ALIGN_INSIDE );

            { Fl_Pack *o = buttons_pack = new Fl_Pack( X, Y, W, 30 );
                o->type( Fl_Pack::HORIZONTAL );
                o->box( FL_NO_BOX );
                { Fl_Button *o = quit_button = new Fl_Button( 0, 0, 50, 50, "&Quit" );
                    o->shortcut( FL_CTRL | 'q' );
                    o->box( FL_UP_BOX );
                    o->callback( cb_handle, (void*)this );
                }
                { Fl_Button *o = refresh_button = new Fl_Button( 0, 0, 70, 50, "&Refresh" );
                    o->shortcut( FL_CTRL | 'r' );
                    o->box( FL_UP_BOX );
                    o->callback( cb_handle, (void*)this );
                }
                { Fl_Button *o = new_button = new Fl_Button( 0, 0, 100, 50, "&New Session" );
                    o->shortcut( FL_CTRL | 'n' );
                    o->box( FL_UP_BOX );
                    o->callback( cb_handle, (void*)this );
                }
                { Fl_Button *o = save_button = new Fl_Button( 0, 0, 105, 50, "&Save" );
                    o->shortcut( FL_CTRL | 's' );
                    o->box( FL_UP_BOX );
                    o->callback( cb_handle, (void*)this );
                }
                { Fl_Button *o = close_button = new Fl_Button( 0, 0, 105, 50, "Save && Close" );
                    o->shortcut( FL_CTRL | 'e' ); // is this a good key?
                    o->box( FL_UP_BOX );
                    o->callback( cb_handle, (void*)this );
                }
                { Fl_Button *o = duplicate_button = new Fl_Button( 0, 0, 105, 50, "Save && &Dupl." );
                    o->shortcut( FL_CTRL | 'd' );
                    o->box( FL_UP_BOX );
                    o->callback( cb_handle, (void*)this );
                }
                { Fl_Button *o = open_button = new Fl_Button( 0, 0, 105, 50, "Save && &Open" );
                    o->shortcut( FL_CTRL | 'o' );
                    o->box( FL_UP_BOX );
                    o->callback( cb_handle, (void*)this );
                }
                { Fl_Button *o = abort_button = new Fl_Button( 0, 0, 160, 50, "Close &without Saving" );
                    o->shortcut( FL_CTRL | 'w' );
                    o->box( FL_UP_BOX );
                    o->callback( cb_handle, (void*)this );
                }

                o->end();
            }

            int SH = 14;

            { Fl_Tile *o = new Fl_Tile( X, Y + 30, W, H - 30 );
                { Fl_Scalepack *o = new Fl_Scalepack( X, Y + 30, 300, H - ( 30 +  SH ) );
                    o->type( FL_VERTICAL );
                    o->spacing( 2 );

                    { new Fl_Box( 0,0,100, 24, "Sessions" );
                    }

                    {
                        Fl_Tree *o = session_browser = new Fl_Tree( X, Y + 50, W / 3, H - ( 50 + SH ) );
                        o->callback( cb_handle, (void *)this );
                        o->sortorder( FL_TREE_SORT_ASCENDING );
                        o->showroot( 0 );
                        o->selectbox( FL_UP_FRAME );
                        o->box( FL_FLAT_BOX );
                        /* o->label( "Sessions" ); */
                        o->end();
                        Fl_Group::current()->resizable( o );
                    } // Fl_Tree
                    o->end();
                }

                Fl_Scalepack *scalepack;
                { Fl_Scalepack *o = scalepack = new Fl_Scalepack( X + 300, Y + 30, W - 300, H - ( 30 + SH ) );
                    o->type( FL_VERTICAL );
                    o->spacing( 2 );

                    {
                        session_name_box = new Fl_Box( 0, 0, 100, 25, "" );
                        session_name_box->labelsize( 20 );
                    }

                    { Fl_Button *o = add_button = new Fl_Button( 0, 0, 100, 25, "&Add Client to Session" );
                        o->shortcut( FL_CTRL | 'a' );
                        o->box( FL_UP_BOX );
                        o->align( FL_ALIGN_CLIP );
                        o->callback( cb_handle, (void*)this );
                    }

                    {
                        Fl_Packscroller *o = new Fl_Packscroller( 0, 0, 100, H - ( 30 + SH ) );
                        o->align( FL_ALIGN_TOP );
                        o->labeltype( FL_SHADOW_LABEL );
                        {
                            Fl_Pack *o = clients_pack = new Fl_Pack( 0, 0, 100, 100 );
                            o->align( FL_ALIGN_TOP );
                            o->spacing( 4 );
                            o->type( Fl_Pack::VERTICAL );
                            o->end();
                        }
                        o->end();
                        Fl_Group::current()->resizable( o );
                    } // Fl_Packscroller
                    o->end();
                    /* Fl_Group::current()->resizable( o ); */
                } // Fl_Scalepack

                { new Fl_Box( X + 1000, Y + 30, 100, H - ( 30 + SH ));
                }

                { Fl_Text_Display *o = status_display = new Fl_Text_Display( X, Y + H - SH, W, SH );
                    o->box( FL_UP_BOX );
                    Fl_Text_Buffer *b = new Fl_Text_Buffer();
                    o->textfont( FL_COURIER ); // Create the "technical log" look&feel
                    o->textsize( 12 );
                    o->buffer(b);
                }

                o->end();
                resizable( o );

            } // Fl_tile

            end();

            deactivate();
        }

    int min_h ( void )
        {
            return 500;
        }

    void
    ping ( void )
        {
            if ( daemon_list.size() )
            {
                foreach_daemon( d )
                {
                    osc->send( (*d)->addr, "/osc/ping" );
                }
            }
            if ( last_ping_response )
            {
                if ( time(NULL) - last_ping_response > 10 )
                {
                    if ( active() )
                    {
                        deactivate();
                        log_status( "Server is not responding..." );
                    }
                }
                else
                {
                    if ( !active() )
                    {
                        log_status( "Server is back." );
                        activate();
                    }
                }
            }
        }


    int init_osc ( void )
        {
            osc = new OSC::Endpoint();

            if ( int r = osc->init( LO_UDP ) )
                return r;

            osc->owner = this;

            osc->add_method( "/error", "sis", osc_handler, osc, "msg" );
            osc->add_method( "/reply", "ss", osc_handler, osc, "msg" );
            osc->add_method( "/reply", "s", osc_handler, osc, "" );

            osc->add_method( "/nsm/server/broadcast", NULL, osc_broadcast_handler, osc, "msg" );
            osc->add_method( "/nsm/gui/server_announce", "s", osc_handler, osc, "msg" );
            osc->add_method( "/nsm/gui/server/message", "s", osc_handler, osc, "msg" );
            osc->add_method( "/nsm/gui/gui_announce", "s", osc_handler, osc, "msg" );
            osc->add_method( "/nsm/gui/session/session", "s", osc_handler, osc, "path,display_name" );
            osc->add_method( "/nsm/gui/session/name", "ss", osc_handler, osc, "path,display_name" );
            osc->add_method( "/nsm/gui/client/new", "ss", osc_handler, osc, "path,display_name" );
            osc->add_method( "/nsm/gui/client/status", "ss", osc_handler, osc, "path,display_name" );
            osc->add_method( "/nsm/gui/client/switch", "ss", osc_handler, osc, "path,display_name" );
            osc->add_method( "/nsm/gui/client/progress", "sf", osc_handler, osc, "path,display_name" );
            osc->add_method( "/nsm/gui/client/dirty", "si", osc_handler, osc, "path,display_name" );
            osc->add_method( "/nsm/gui/client/has_optional_gui", "s", osc_handler, osc, "path,display_name" );
            osc->add_method( "/nsm/gui/client/gui_visible", "si", osc_handler, osc, "path,display_name" );
            osc->add_method( "/nsm/gui/client/label", "ss", osc_handler, osc, "path,display_name" );

            osc->start();

            return 0;
        }


    void announce ( const char *nsm_url )
        {
            /* Daemon *d = new Daemon; */

            /* d->url = nsm_url; */
            lo_address nsm_addr = lo_address_new_from_url( nsm_url );
//            d->is_child = true;

            /* daemon_list.push_back( d ); */

            osc->send( nsm_addr, "/nsm/gui/gui_announce" );
        }

private:

    static int osc_broadcast_handler ( const char *path, const char *, lo_arg **, int argc, lo_message msg, void * )
        {
            if ( ! argc )
                /* need at least one argument... */
                return 0;

            MESSAGE( "Relaying broadcast" );

            foreach_daemon( d )
            {
                char *u1 = lo_address_get_url( (*d)->addr );
                char *u2 = lo_address_get_url( lo_message_get_source( msg ) );

                if ( strcmp( u1, u2 ) )
                {
                    osc->send( (*d)->addr, path, msg );
                }

                free( u1 );
                free( u2 );
            }

            return 0;
        }

    static int osc_handler ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
        {
//            OSC_DMSG();

            NSM_Controller *controller = (NSM_Controller*)((OSC::Endpoint*)user_data)->owner;

            Fl::lock();

            if ( !strcmp( path, "/nsm/gui/server/message" ) && !strcmp( types, "s" ) )
            {
                controller->log_status( &argv[0]->s );
            }
            else if ( !strcmp( path, "/nsm/gui/session/session" ) &&
                 ! strcmp( types, "s" ) )
            {
                controller->add_session_to_list( &argv[0]->s );
            }
            else if ( !strcmp( path, "/nsm/gui/gui_announce" ) )
            {
                /* pre-existing server is replying to our announce message */
                controller->activate();

                lo_address nsm_addr = lo_message_get_source( msg );

                osc->send( nsm_addr, "/nsm/server/list" );
            }
            else if ( !strcmp( path, "/nsm/gui/server_announce" ) )
            {
                /* must be a server we launched */

                controller->activate();

                Daemon *d = new Daemon;

                d->url = lo_address_get_url( lo_message_get_source( msg ) );
                d->addr = lo_address_new_from_url( d->url );
                d->is_child = true;

                daemon_list.push_back( d );

                osc->send( d->addr, "/nsm/server/list" );
            }
            else if ( !strcmp( path, "/nsm/gui/session/name" ) &&
                      !strcmp( types, "ss" ))
            {
                controller->session_name( &argv[0]->s );

                if ( !strcmp( &argv[0]->s, "" ) )
                {
                    controller->session_browser->deselect_all();
                }
                else
                {
                    Fl_Tree_Item *o = controller->session_browser->find_item( &argv[1]->s );
                    if ( o )
                    {
                        controller->session_browser->select_only( o, 0 );
                        controller->session_browser->show_item( o, 0 );
                    }
                }
            }
            else if (!strcmp( path, "/error" ) &&
                     !strcmp( types, "sis" ) )
            {
                int err = argv[1]->i;

                if ( err != 0 )
                {
                    char *s;
                    asprintf( &s, "Command %s failed with:\n\n%s", &argv[0]->s, &argv[2]->s );

                    Fl::awake(fl_awake_alert, s);
                }
            }
            else if (!strcmp( path, "/reply" ) && argc && 's' == *types )
            {
                if ( !strcmp( &argv[0]->s, "/nsm/server/list" ) )
                {
                    controller->add_session_to_list( &argv[1]->s );
                }
                else if ( !strcmp( &argv[0]->s, "/osc/ping" ) )
                {
                    last_ping_response = time( NULL );
                }
                else if ( ! strcmp( types, "ss" ) )
                {
                    MESSAGE( "%s says %s", &argv[0]->s, &argv[1]->s);
                    controller->log_status( &argv[1]->s );
                }
            }

            if ( !strncmp( path, "/nsm/gui/client/", strlen( "/nsm/gui/client/" ) ) )
            {
                if ( !strcmp( path, "/nsm/gui/client/new" ) &&
                     !strcmp( types, "ss" ) )
                {
                    controller->client_new( &argv[0]->s, &argv[1]->s );
                }
                else
                {
                    NSM_Client *c = controller->client_by_id( &argv[0]->s );

                    if ( c )
                    {
                        if ( !strcmp( path, "/nsm/gui/client/status" ) &&
                             !strcmp( types, "ss" ))
                        {
                            controller->client_pending_command( c, &argv[1]->s );
                        }
                        else if ( !strcmp( path, "/nsm/gui/client/progress" ) &&
                                  !strcmp( types, "sf" ))
                        {
                            c->progress( argv[1]->f );
                        }
                        else if ( !strcmp( path, "/nsm/gui/client/dirty" ) &&
                                  !strcmp( types, "si" ))
                        {
                            c->dirty(  argv[1]->i );
                        }
                        else if ( !strcmp( path, "/nsm/gui/client/gui_visible" ) &&
                                  !strcmp( types, "si" ))
                        {
                            c->gui_visible(  argv[1]->i );
                        }
                        else if ( !strcmp( path, "/nsm/gui/client/label" ) &&
                                  !strcmp( types, "ss" ))
                        {
                            c->client_label( &argv[1]->s );
                        }
                        else if ( !strcmp( path, "/nsm/gui/client/has_optional_gui" ) &&
                                  !strcmp( types, "s" ))
                        {
                            c->has_optional_gui();
                        }
                        else if ( !strcmp( path, "/nsm/gui/client/switch" ) &&
                                  !strcmp( types, "ss" ))
                        {
                            c->client_id( &argv[1]->s );
                        }
                    }
                    else
                        MESSAGE( "Got message %s from unknown client", path );
                }
            }

            Fl::unlock();
            Fl::awake();

            return 0;
        }
};


static NSM_Controller *controller;

void
ping ( void * )
{
    controller->ping();
    Fl::repeat_timeout( 1.0, ping, NULL );
}

void
cb_main ( Fl_Widget *, void * )
{
    if ( Fl::event_key() != FL_Escape )
    {
        int children = 0;
        foreach_daemon ( d )
        {
            if ( (*d)->is_child )
                ++children;
        }

        if ( children )
        {
            if ( strlen( controller->session_name() ) )
            {
                fl_message( "%s", "You have to close the session before you can quit." );
                return;
            }
        }

        while ( Fl::first_window() ) Fl::first_window()->hide();
    }
}

int
main (int argc, char **argv )
{
    Fl::scheme( "gtk+" );

    Fl::visual(FL_DOUBLE|FL_INDEX); // FLKT Double_Window: "higly recommended […] put before the first show() of any window in your program"

    fl_register_images();

    Fl::lock();

    Fl_Double_Window *main_window;

    {
        Fl_Double_Window *o = main_window = new Fl_Double_Window( 800, 600, APP_TITLE );
        {
            main_window->xclass( APP_NAME );

            Fl_Widget *o = controller = new NSM_Controller( 0, 0, main_window->w(), main_window->h(), NULL );
            controller->session_name( "" );

            Fl_Group::current()->resizable(o);
        }
        o->end();

        o->size_range( main_window->w(), controller->min_h(), 0, 0 );

        o->callback( (Fl_Callback*)cb_main, main_window );

        o->show( 0, NULL );
    }

    //Setting colors only after main window creation.
    //We keep them all in once place instead of setting them in the widgets
    Fl::set_color( FL_BACKGROUND_COLOR, 37, 40, 45 ); //These are the colors used as backgrounds by almost all widgets and used to draw the edges of all the boxtypes.
    Fl::set_color( FL_BACKGROUND2_COLOR, 55, 61, 69 ); //This color is used as a background by Fl_Input and other text widgets.
    Fl::set_color( FL_FOREGROUND_COLOR, 223, 237, 255 );
    Fl::set_color( FL_INACTIVE_COLOR, 255, 0, 0 ); // Not used
    Fl::set_color( FL_SELECTION_COLOR, 80, 84, 92 ); // e.g. the currently selected session
    Fl::reload_scheme();
    controller->session_browser->item_labelfgcolor( fl_rgb_color (213, 227, 245 ) ); // a bit darker than foreground



    static struct option long_options[] =
        {
            { "nsm-url", required_argument, 0, 'n' },
            { "help", no_argument, 0, 'h' },
            { 0, 0, 0, 0 }
        };

    int option_index = 0;
    int c = 0;

    while ( ( c = getopt_long_only( argc, argv, "", long_options, &option_index  ) ) != -1 )
    {
        switch ( c )
        {
            case 'n':
            {
                MESSAGE( "Adding %s to daemon list", optarg );
                Daemon *d = new Daemon;

                d->url = optarg;
                d->addr = lo_address_new_from_url( optarg );

                daemon_list.push_back( d );
                break;
            }
            case 'h':
                //Print usage message according to POSIX.1-2017
                const char *usage =
                "nsm-legacy-gui - FLTK GUI for the 'New Session Manager'\n\n"
                "Usage:\n"
                "  nsm-legacy-gui\n"
                "  nsm-legacy-gui --help\n"
                "\n"
                "Options:\n"
                "  --help                Show this screen\n"
                "  --nsm-url url         Connect to a running nsmd [Example: osc.udp://mycomputer.localdomain:38356/].\n"
                "  --                    Everything after -- will be given to nsmd as server options. See nsmd --help .\n"
                "\n"
                "For backwards compatibility this executable also exist as symlink 'non-session-manager'\n"
                "\n"
                "";
                puts ( usage );
                exit(0);
                break;
        }
    }

    const char *nsm_url = getenv( "NSM_URL" );

    if ( nsm_url )
    {
        MESSAGE( "Found NSM URL of \"%s\" in environment, attempting to connect.", nsm_url );

        Daemon *d = new Daemon;

        d->url = nsm_url;
        d->addr = lo_address_new_from_url( nsm_url );

        daemon_list.push_back( d );
    }

    if ( controller->init_osc() )
        FATAL( "Could not create OSC server" );

    if ( daemon_list.size() )
    {
        foreach_daemon ( d )
        {
            controller->announce( (*d)->url );
        }
    }
    else
    {
        /* start a new daemon... */
        MESSAGE( "Starting daemon..." );

        char *url = osc->url();

        if ( ! fork() )
        {
            /* pass non-option arguments on to daemon */

            char *args[4 + argc - optind];

            int i = 0;
            args[i++] = strdup("nsmd");
            args[i++] = strdup("--gui-url");
            args[i++] = url;

            for ( ; optind < argc; i++, optind++ )
            {
                MESSAGE( "Passing argument: %s", argv[optind] );
                args[i] = argv[optind];
            }

            args[i] = 0;

            if ( -1 == execvp( "nsmd", args ) )
            {
                FATAL( "Error starting process: %s", strerror( errno ) );
            }
        }

        free(url);
    }

    Fl::add_timeout( 1.0, ping, NULL );
    Fl::run();

    foreach_daemon ( d )
    {
        if ( (*d)->is_child )
        {
            MESSAGE( "Telling server to quit" );
            osc->send( (*d)->addr, "/nsm/server/quit" );
        }
    }

    return 0;
}

07070100000043000081A40000000000000000000000016258A65C00003A97000000000000000000000000000000000000004600000000new-session-manager-1.6.0+git.20220415.0f6719c/src/nsm-legacy-gui.svg<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="68.791656mm"
   height="68.791656mm"
   viewBox="0 0 68.791656 68.791656"
   version="1.1"
   id="svg8"
   sodipodi:docname="icons.svg"
   inkscape:version="1.0 (4035a4fb49, 2020-05-01)">
  <defs
     id="defs2" />
  <sodipodi:namedview
     units="px"
     id="base"
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0.0"
     inkscape:pageshadow="2"
     inkscape:zoom="0.98994949"
     inkscape:cx="-233.62958"
     inkscape:cy="14.02435"
     inkscape:document-units="mm"
     inkscape:current-layer="layer1"
     inkscape:document-rotation="0"
     showgrid="false"
     inkscape:window-width="2558"
     inkscape:window-height="1398"
     inkscape:window-x="0"
     inkscape:window-y="20"
     inkscape:window-maximized="1" />
  <metadata
     id="metadata5">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     transform="translate(-232.45202,-114.10416)"
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1">
    <g
       id="nsm"
       inkscape:label="nsm"
       transform="translate(-172.56864)">
      <title
         id="title888">nsm</title>
      <rect
         y="114.63333"
         x="71.133331"
         height="67.73333"
         width="67.73333"
         id="rect10"
         style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
      <text
         xml:space="preserve"
         style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
         x="105.32493"
         y="169.25755"
         id="text852"><tspan
           sodipodi:role="line"
           x="105.32493"
           y="169.25755"
           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
           id="tspan850">NSM</tspan></text>
    </g>
    <g
       transform="translate(-170.13191,80.919145)"
       inkscape:label="jackpatch"
       id="jackpatch">
      <title
         id="title890">jackpatch</title>
      <rect
         style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
         id="rect892"
         width="67.73333"
         height="67.73333"
         x="71.133331"
         y="114.63333" />
      <text
         id="text896"
         y="165.8237"
         x="106.38719"
         style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
         xml:space="preserve"><tspan
           id="tspan894"
           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
           y="165.8237"
           x="106.38719"
           sodipodi:role="line">JP</tspan></text>
    </g>
    <g
       id="proxy"
       inkscape:label="proxy">
      <title
         id="title940">proxy</title>
      <rect
         y="37.223072"
         x="-99.065941"
         height="67.73333"
         width="67.73333"
         id="rect902"
         style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
      <text
         xml:space="preserve"
         style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
         x="-65.639175"
         y="93.759422"
         id="text906"><tspan
           sodipodi:role="line"
           x="-65.639175"
           y="93.759422"
           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
           id="tspan904">PRX</tspan></text>
    </g>
    <path
       d="m 231.64549,37.223072 h 67.73333 V 104.9564 h -67.73333 z"
       style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
       id="rect1679" />
    <g
       style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
       id="text1683"
       aria-label="PRX">
      <path
         id="path1690"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 238.98532,48.420053 0.0677,45.339369 h 4.87229 V 72.578493 l 0.27069,-0.06767 h 4.60161 c 1.42108,0 2.63915,-0.406024 3.65421,-1.150401 1.01506,-0.744378 1.48876,-1.962451 1.48876,-3.654218 V 53.427685 c 0,-1.96245 -0.4737,-3.315864 -1.42109,-3.992571 -0.94739,-0.676707 -2.16546,-1.015061 -3.72188,-1.015061 z m 4.93996,4.872291 h 3.31587 c 0.47369,0 0.81205,0.135341 1.1504,0.473695 0.33835,0.338353 0.4737,0.744377 0.4737,1.218072 v 10.962654 c 0,0.473694 -0.13535,0.812048 -0.4737,1.150401 -0.33835,0.338354 -0.67671,0.473695 -1.1504,0.473695 h -3.31587 z" />
      <path
         id="path1692"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 262.66997,53.292344 h 3.31586 c 0.4737,0 0.81205,0.135341 1.15041,0.473695 0.33835,0.338353 0.47369,0.744377 0.47369,1.218072 v 10.962654 c 0,0.473694 -0.13534,0.812048 -0.47369,1.150401 -0.33836,0.338354 -0.67671,0.473695 -1.15041,0.473695 h -3.31586 z m -4.93996,-4.872291 0.0677,45.339369 H 262.67 v -21.2486 h 1.21807 l 4.19559,21.2486 h 4.87229 L 268.69266,72.24014 c 0.60904,-0.203012 1.42109,-0.609037 2.50382,-1.218073 0.94739,-0.609036 1.48875,-1.759438 1.48875,-3.451206 V 53.427685 c 0,-1.285743 -0.47369,-2.436145 -1.48875,-3.518876 -1.01506,-1.015061 -2.16547,-1.488756 -3.51888,-1.488756 z" />
      <path
         id="path1694"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 275.6627,48.420053 5.0753,23.346392 -4.80462,21.789965 h 5.00763 l 2.90984,-13.53414 2.90984,13.53414 h 4.93996 l -4.73695,-21.789965 5.07531,-23.346392 h -5.14298 l -3.04518,14.752213 -3.04518,-14.752213 z" />
    </g>
    <path
       d="m 230.75774,195.55247 h 67.73333 v 67.73333 h -67.73333 z"
       style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
       id="rect1700" />
    <path
       d="m 256.70029,252.35951 v -50.95604 h -5.0753 v 49.46729 c 0,0.47369 -0.13534,0.81204 -0.4737,1.1504 -0.33835,0.33835 -0.6767,0.47369 -1.1504,0.47369 h -1.75944 v 4.93996 h 3.04518 c 3.58655,0 5.41366,-1.69176 5.41366,-5.0753 z"
       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;line-height:1.25;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
       id="path1712" />
    <path
       d="m 266.05212,201.40347 0.0677,45.33937 h 4.87229 v -21.18093 l 0.27068,-0.0677 h 4.60161 c 1.42108,0 2.63916,-0.40602 3.65422,-1.1504 1.01506,-0.74438 1.48875,-1.96245 1.48875,-3.65422 v -14.27851 c 0,-1.96245 -0.47369,-3.31587 -1.42108,-3.99257 -0.94739,-0.67671 -2.16546,-1.01507 -3.72189,-1.01507 z m 4.93996,4.87229 h 3.31586 c 0.4737,0 0.81205,0.13535 1.1504,0.4737 0.33836,0.33835 0.4737,0.74438 0.4737,1.21807 v 10.96266 c 0,0.47369 -0.13534,0.81204 -0.4737,1.1504 -0.33835,0.33835 -0.6767,0.47369 -1.1504,0.47369 h -3.31586 z"
       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;line-height:1.25;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
       id="path1714" />
    <path
       d="m 232.98119,114.63333 h 67.73333 v 67.73333 h -67.73333 z"
       style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
       id="rect1875" />
    <g
       style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
       id="text1879"
       aria-label="NSM">
      <path
         id="path1886"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 236.41654,123.6475 0.13535,0.27068 v 45.33937 h 5.00763 v -29.70743 l 8.79719,29.70743 h 5.34598 l -0.13534,-0.40602 V 123.6475 h -5.21064 v 29.63977 l -8.72952,-29.63977 z" />
      <path
         id="path1888"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 269.64283,169.52824 c 1.35341,0 2.50382,-0.4737 3.45121,-1.35342 1.01506,-0.94739 1.48875,-2.09779 1.48875,-3.4512 v -14.48153 c 0,-1.28575 -0.47369,-2.43615 -1.35341,-3.45121 -0.94739,-0.94739 -2.16546,-1.6241 -3.58655,-2.09779 l -3.65422,-1.1504 c -0.40602,-0.13534 -0.81205,-0.4737 -1.1504,-0.94739 -0.33835,-0.54137 -0.47369,-1.01506 -0.47369,-1.48876 v -10.82731 c 0,-0.4737 0.13534,-0.81205 0.47369,-1.1504 0.33835,-0.33836 0.67671,-0.4737 1.1504,-0.4737 h 1.6241 c 0.47369,0 0.81205,0.13534 1.1504,0.4737 0.33836,0.33835 0.4737,0.6767 0.4737,1.1504 v 3.51888 h 5.00763 v -5.27832 c 0,-1.42108 -0.4737,-2.57148 -1.42109,-3.51887 -1.01506,-0.94739 -2.16546,-1.42109 -3.51887,-1.42109 h -4.93996 c -1.42109,0 -2.57149,0.4737 -3.51888,1.42109 -0.94739,0.94739 -1.42108,2.09779 -1.42108,3.51887 v 14.21085 c 0,1.28574 0.47369,2.50381 1.35341,3.51888 0.94739,1.01506 2.16546,1.69176 3.58655,2.16546 l 3.65421,1.08273 c 0.4737,0.13534 0.81205,0.40602 1.15041,0.87972 0.33835,0.47369 0.47369,0.87972 0.47369,1.35341 v 11.16567 c 0,0.47369 -0.13534,0.87972 -0.47369,1.21807 -0.33836,0.33835 -0.67671,0.47369 -1.15041,0.47369 h -1.62409 c -0.4737,0 -0.81205,-0.13534 -1.1504,-0.47369 -0.33836,-0.33835 -0.4737,-0.74438 -0.4737,-1.21807 v -3.51888 h -5.00763 v 5.34599 c 0,1.35341 0.47369,2.50381 1.42108,3.4512 1.01506,0.87972 2.16547,1.35342 3.51888,1.35342 z" />
      <path
         id="path1890"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 278.30458,123.91818 0.13534,45.33937 h 5.14298 v -31.94057 l 4.19558,15.97029 h 0.67671 l 3.99257,-15.97029 v 31.94057 h 5.27831 v -45.33937 h -4.80462 l -4.80462,15.0229 -4.87229,-15.0229 z" />
    </g>
    <text
       id="text1896"
       y="101.68526"
       x="-171.08479"
       style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
       xml:space="preserve"><tspan
         style="stroke-width:0.264583"
         y="101.68526"
         x="-171.08479"
         id="tspan1894"
         sodipodi:role="line">Texts and</tspan><tspan
         id="tspan1898"
         style="stroke-width:0.264583"
         y="114.91438"
         x="-171.08479"
         sodipodi:role="line">Objects</tspan></text>
    <text
       id="text1902"
       y="105.24335"
       x="349.29498"
       style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
       xml:space="preserve"><tspan
         style="stroke-width:0.264583"
         y="105.24335"
         x="349.29498"
         id="tspan1900"
         sodipodi:role="line">Converted</tspan><tspan
         id="tspan1904"
         style="stroke-width:0.264583"
         y="118.47247"
         x="349.29498"
         sodipodi:role="line">to paths</tspan></text>
    <text
       id="text1908"
       y="39.494576"
       x="41.722755"
       style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
       xml:space="preserve"><tspan
         style="stroke-width:0.264583"
         y="39.494576"
         x="41.722755"
         id="tspan1906"
         sodipodi:role="line">Select Icon, Ctrl+Shift+R</tspan><tspan
         id="tspan1910"
         style="stroke-width:0.264583"
         y="52.723701"
         x="41.722755"
         sodipodi:role="line">to resize to selection.</tspan><tspan
         id="tspan1912"
         style="stroke-width:0.264583"
         y="65.952827"
         x="41.722755"
         sodipodi:role="line">File -&gt; Save Copy </tspan><tspan
         id="tspan1914"
         style="stroke-width:0.264583"
         y="79.181946"
         x="41.722755"
         sodipodi:role="line">Inkscape SVG works as icon</tspan></text>
  </g>
</svg>
07070100000044000081A40000000000000000000000016258A65C0000316E000000000000000000000000000000000000004500000000new-session-manager-1.6.0+git.20220415.0f6719c/src/nsm-proxy-gui.cpp
/*******************************************************************************/
/* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager")     */
/* Copyright (C) 2020- Nils Hilbricht                                          */
/*                                                                             */
/* This file is part of New-Session-Manager                                    */
/*                                                                             */
/* New-Session-Manager 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.                                         */
/*                                                                             */
/* New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
/*******************************************************************************/

#pragma GCC diagnostic ignored "-Wunused-parameter"


#define _MODULE_ "nsm-proxy-gui"

#define APP_NAME "NSM Proxy"
#define APP_TITLE "NSM Proxy"

#include <FL/Fl_File_Chooser.H>
#include <FL/Fl_Text_Display.H>
#include "NSM_Proxy_UI.H"
#include <lo/lo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>


lo_server losrv;
lo_address nsmp_addr;

static NSM_Proxy_UI *ui;

static char *client_error;

int
osc_update ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    //Updates are arriving one OSC message at a time.

    printf( "Got update for %s\n", path );

    Fl::lock();

    if (!strcmp( path, "/nsm/proxy/label" ))
        ui->label_input->value( &argv[0]->s );
    else if (!strcmp( path, "/nsm/proxy/arguments" ))
        ui->arguments_input->value( &argv[0]->s );
    else if (!strcmp( path, "/nsm/proxy/executable" )) {
        ui->executable_input->value( &argv[0]->s );
        if ( strcmp( &argv[0]->s , "") ) {
            //We want to avoid that the button is always labeled 'Start', creating the
            //false impression that a new sub-client instance is started each time you press it.
            //If the string is empty there is no program running at the moment.
            //This does not cover all cases but gets us there 90%, which is good enough for something cosmetic.
            ui->start_button->label("Ok");
        }
    }
    else if (!strcmp( path, "/nsm/proxy/config_file" ))
        ui->config_file_input->value( &argv[0]->s );
    else if (!strcmp( path, "/nsm/proxy/save_signal" ))
    {
        if ( argv[0]->i == SIGUSR1 )
            ui->save_signal_choice->value( 1 );
        else if ( argv[0]->i == SIGUSR2 )
            ui->save_signal_choice->value( 2 );
        else if ( argv[0]->i == SIGINT )
            ui->save_signal_choice->value( 3 );
        else
            ui->save_signal_choice->value( 0 );
    }
    else if (!strcmp( path, "/nsm/proxy/stop_signal" ))
    {
        if ( argv[0]->i == SIGTERM )
            ui->stop_signal_choice->value( 0 );
        else if ( argv[0]->i == SIGINT )
            ui->stop_signal_choice->value( 1 );
        else if ( argv[0]->i == SIGHUP )
            ui->stop_signal_choice->value( 2 );
    }
    if (!strcmp( path, "/nsm/proxy/client_error" ))
    {
        if ( client_error != NULL )
            free(client_error);

        client_error = NULL;

        if ( strlen(&argv[0]->s) > 0 )
            client_error = strdup(&argv[0]->s);
    }

    Fl::unlock();

    return 0;
}


void
init_osc ( const char *osc_port )
{

    lo_server_thread loth = lo_server_thread_new( osc_port, NULL );
    losrv = lo_server_thread_get_server( loth );

//error_handler );

    char *url = lo_server_get_url(losrv);
    printf("OSC: %s\n",url);
    free(url);

    /* GUI */

    lo_server_thread_add_method( loth, "/nsm/proxy/executable", "s", osc_update, NULL );
    lo_server_thread_add_method( loth, "/nsm/proxy/arguments", "s", osc_update, NULL );
    lo_server_thread_add_method( loth, "/nsm/proxy/config_file", "s", osc_update, NULL );
    lo_server_thread_add_method( loth, "/nsm/proxy/label", "s", osc_update, NULL );
    lo_server_thread_add_method( loth, "/nsm/proxy/save_signal", "i", osc_update, NULL );
    lo_server_thread_add_method( loth, "/nsm/proxy/stop_signal", "i", osc_update, NULL );
    lo_server_thread_add_method( loth, "/nsm/proxy/client_error", "s", osc_update, NULL );

    lo_server_thread_start( loth );
}

/*****************/
/* GUI Callbacks */
/*****************/

void
handle_kill ( Fl_Widget *o, void *v )
{
    lo_send_from( nsmp_addr, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/kill", "" );
}

void
handle_start ( Fl_Widget *o, void *v )
{
    //Executed when Start Button is clicked.
    //"/nsm/proxy/start" for the sub-client is always sent, no matter if the software already runs.
    //nsm-proxy.cpp handles the redundancy.
    lo_send_from( nsmp_addr, losrv,  LO_TT_IMMEDIATE, "/nsm/proxy/start", "sss",
                  ui->executable_input->value(),
                  ui->arguments_input->value(),
                  ui->config_file_input->value() );
}

void
handle_label ( Fl_Widget *o, void *v )
{
    lo_send_from( nsmp_addr, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/label", "s",
                  ui->label_input->value() );
}

void
handle_executable ( Fl_Widget *o, void *v )
{
    //Autofill the label field when the executable name is edited
    ui->label_input->value( ui->executable_input->value() );
    //This does not trigger handle_label so we call it manually
    lo_send_from( nsmp_addr, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/label", "s",
                  ui->label_input->value() );
}


void
handle_config_file ( Fl_Widget *o, void *v )
{
}

void
handle_config_file_browse ( Fl_Widget *o, void *v )
{
    const char * file = fl_file_chooser( "Pick file", "*", NULL, 1 );

    ui->config_file_input->value( file );
}

void
handle_save_signal ( Fl_Widget *o, void *v )
{
    int sig = 0;

    const char* picked = ui->save_signal_choice->mvalue()->label();

    if ( !strcmp( picked, "SIGUSR1" ) )
        sig = SIGUSR1;
    else if ( !strcmp( picked, "SIGUSR2" ) )
        sig = SIGUSR2;
    else if ( !strcmp( picked, "SIGINT" ) )
        sig = SIGINT;

    lo_send_from( nsmp_addr, losrv,  LO_TT_IMMEDIATE,"/nsm/proxy/save_signal", "i",
                  sig );
}

void
handle_stop_signal ( Fl_Widget *o, void *v )
{
    int sig = SIGTERM;

    const char* picked = ui->stop_signal_choice->mvalue()->label();

    if ( !strcmp( picked, "SIGTERM" ) )
        sig = SIGTERM;
    else if ( !strcmp( picked, "SIGINT" ) )
        sig = SIGINT;
    else if ( !strcmp( picked, "SIGHUP" ) )
        sig = SIGHUP;

    lo_send_from( nsmp_addr, losrv,  LO_TT_IMMEDIATE,"/nsm/proxy/stop_signal", "i",
                  sig );
}

void
connect_ui ( void )
{
    ui->executable_input->callback( handle_executable, NULL );
    ui->config_file_input->callback( handle_config_file, NULL );
    ui->kill_button->callback( handle_kill, NULL );
    ui->start_button->callback( handle_start, NULL );
    ui->save_signal_choice->callback( handle_save_signal, NULL );
    ui->stop_signal_choice->callback( handle_stop_signal, NULL );
    ui->label_input->callback( handle_label, NULL );
    ui->config_file_browse_button->callback( handle_config_file_browse, NULL );
}



void cb_dismiss_button ( Fl_Widget *w, void *v )
{
    w->window()->hide();
}

void
check_error ( void *v )
{
    if ( client_error )
    {
        {
            Fl_Double_Window *o = new Fl_Double_Window(600,300+15,"Abnormal Termination");
            //Create a new floating Window that shows an error message.
            {
                Fl_Box *o = new Fl_Box(0+15,0+15,600-30,50);
                o->box(FL_BORDER_BOX);
                o->color(FL_RED);
                o->labelcolor(FL_WHITE);
                o->align(FL_ALIGN_CENTER|FL_ALIGN_WRAP);
                o->copy_label( client_error );
            }
            {
                Fl_Text_Display *o = new Fl_Text_Display(0+15,50+15,600-30,300-75-30);
                o->buffer(new Fl_Text_Buffer());
                o->buffer()->loadfile( "error.log" );
            }
            {
                Fl_Button *o = new Fl_Button(600-75-15,300-25,75,25,"Dismiss");
                o->callback(cb_dismiss_button,0);
            }

            o->show();
        }

        free(client_error);
        client_error = NULL;
    }

    Fl::repeat_timeout( 0.5f, check_error, v );
}

int
main ( int argc, char **argv )
{
    //The NSM-Proxy GUI is a client that communicates with another binary, nsm-proxy via OSC.
    //This GUI executable is actually closed and restarted each time you show/hide the GUI.
    //In other words: it is not a persistent GUI state.


    //Command line parameters
    const char * gui_url = NULL;
    static struct option long_options[] =
    {
        { "connect-to", required_argument, 0, 'u' },
        { "help", no_argument, 0, 'h' },
        { 0, 0, 0, 0 }
    };
    int option_index = 0;
    int c = 0;
    while ( ( c = getopt_long_only( argc, argv, "", long_options, &option_index  ) ) != -1 )
    {
        switch ( c )
        {
            case 'u':
            {
                gui_url = optarg;
                break;
            }

            case 'h':
            {
                const char *usage =
                "nsm-proxy-gui - GUI for nsm-proxy, a wrapper for executables without direct NSM-Support.\n\n"
                "Usage:\n"
                "  nsm-proxy-gui --help\n"
                "  nsm-proxy-gui --connect-to\n"
                "\n"
                "Options:\n"
                "  --help                Show this screen\n"
                "  --connect-to          Connect to running nsm-proxy\n"
                "\n\n"
                "nsmd-proxy-gui is usually not called by the user directly,\n"
                "but autostarted when nsm-proxy is added to a session (through a GUI).\n"
                "";
                puts ( usage );
                exit(0);
                break;
            }
        }
    }

    if ( gui_url == NULL )
        exit(1);

    init_osc( NULL );

    nsmp_addr = lo_address_new_from_url( gui_url );

    if  ( ! nsmp_addr )
        exit(1);

    printf( "Connecting to nsm-proxy at: %s\n", gui_url );

    ui = new NSM_Proxy_UI;

    Fl::scheme( "gtk+" );
    Fl::visual(FL_DOUBLE|FL_INDEX); // FLKT Double_Window: "higly recommended […] put before the first show() of any window in your program"

    Fl_Double_Window *w = ui->make_window();

    connect_ui();

    lo_send_from( nsmp_addr, losrv,  LO_TT_IMMEDIATE, "/nsm/proxy/update", "" );

    //The config file option allows the user to choose a different config file for the proxy settings.
    //This does more harm than good, so we hide the gui field.
    ui->config_file_input->hide();

    w->show();

    Fl::lock();

    //Setting colors only after main window creation.
    //We keep them all in once place instead of setting them in the widgets
    //Colors are the same as nsm-legacy-gui.cpp . If one changes you need to change the other by hand.


    Fl::set_color( 55, 223, 237, 255 ); //Override FLUID palette with RGB Value. 55 is label text. Same as FL_FOREGROUND_COLOR
    Fl::set_color( 41, 55, 61, 69 ); //Override FLUID palette with RGB Value. 41 is label background

    Fl::set_color( FL_DARK1, 37, 40, 45 ); //Main window background
    Fl::set_color( FL_BACKGROUND_COLOR, 37, 40, 45 ); //These are the colors used as backgrounds by almost all widgets and used to draw the edges of all the boxtypes.
    Fl::set_color( FL_BACKGROUND2_COLOR, 55, 61, 69 ); //This color is used as a background by Fl_Input and other text widgets.
    Fl::set_color( FL_FOREGROUND_COLOR, 223, 237, 255 );
    Fl::set_color( FL_INACTIVE_COLOR, 255, 0, 0 ); // Not used
    Fl::set_color( FL_SELECTION_COLOR, 80, 84, 92 ); // e.g. the currently selected session
    Fl::reload_scheme();


    Fl::add_timeout( 0.5f, check_error, NULL );

    Fl::run();

    return 0;
}
07070100000045000081A40000000000000000000000016258A65C0000512A000000000000000000000000000000000000004100000000new-session-manager-1.6.0+git.20220415.0f6719c/src/nsm-proxy.cpp
/*******************************************************************************/
/* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager")     */
/* Copyright (C) 2020- Nils Hilbricht                                          */
/*                                                                             */
/* This file is part of New-Session-Manager                                    */
/*                                                                             */
/* New-Session-Manager 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.                                         */
/*                                                                             */
/* New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
/*******************************************************************************/

#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-result"

#define _MODULE_ "nsm-proxy"
#define APP_NAME "NSM Proxy"
#define APP_TITLE "NSM Proxy"

#include "debug.h"

#include <lo/lo.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/signalfd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <getopt.h>

static lo_server losrv;
static lo_address nsm_addr;
static lo_address gui_addr;
static int nsm_is_active;
static char *project_file;
static int die_now = 0;
static int signal_fd;

static char *nsm_client_id;
static char *nsm_display_name;

#define CONFIG_FILE_NAME "nsm-proxy.config"

void show_gui ( void );

class NSM_Proxy {

    char *_label;
    char *_executable;
    char *_config_file;
    char *_arguments;
    int _save_signal;
    int _stop_signal;
    int _pid;
    char *_client_error;

public:

    int stop_signal ( void ) {return _stop_signal;}

    NSM_Proxy ( )
        {
            _label = _executable = _arguments = _config_file = 0;
            _save_signal = 0;
            _stop_signal = SIGTERM;
            _pid = 0;
            _client_error = 0;
        }

    ~NSM_Proxy ( )
        {
        }

    void handle_client_death ( int status )
        {
            printf( "proxied process died unexpectedly... not dying\n" );
            /* proxied process died unexpectedly */

            if ( _client_error != NULL )
                free(_client_error);

            asprintf(&_client_error, "The proxied process terminated abnormally during invocation. Exit status: %i.", status );

            show_gui();

            _pid = 0;
        }

    void kill ( void )
        {
            if ( _pid )
            {
                ::kill( _pid, _stop_signal );
            }
        }

    bool start ( const char *executable, const char *arguments, const char *config_file )
        {
            if ( _executable )
                free( _executable );
            if ( _arguments )
                free( _arguments );
            if ( _config_file )
                free( _config_file );

            _executable = strdup( executable );

            if ( arguments )
                _arguments = strdup( arguments );
            else
                _arguments = NULL;

            if ( config_file )
                _config_file = strdup( config_file );
            else
                _config_file = NULL;

            return start();
        }

    bool start ( void )
        {
            dump( project_file );

            if ( _pid )
                /* already running */
                return true;

            if ( !_executable )
            {
                WARNING( "Executable is null." );
                return false;
            }

            int pid;
            if ( ! (pid = fork()) )
            {
                MESSAGE( "Launching %s\n", _executable );

//                char *args[] = { strdup( executable ), NULL };

                char *cmd;

                if ( _arguments )
                    asprintf( &cmd, "exec %s %s >error.log 2>&1", _executable, _arguments );
                else
                    asprintf( &cmd, "exec %s  >error.log 2>&1", _executable );

                char *args[] = { strdup("/bin/sh"), strdup( "-c" ), cmd, NULL };

                setenv( "NSM_CLIENT_ID", nsm_client_id, 1 );
                setenv( "NSM_SESSION_NAME", nsm_display_name, 1 );
                if ( _config_file )
                    setenv( "CONFIG_FILE", _config_file, 1 );
                unsetenv( "NSM_URL" );

                if ( -1 == execvp( "/bin/sh", args ) )
                {
                    WARNING( "Error starting process: %s", strerror( errno ) );
                    exit(1);
                }
            }

            _pid = pid;

            return _pid > 0;
        }

    void save_signal ( int s )
        {
            _save_signal = s;
        }

    void stop_signal ( int s )
        {
            _stop_signal = s;
        }

    void label ( const char *s )
        {
            if ( _label )
                free( _label );

            _label = strdup( s );

            lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/label", "s", _label );
        }

    void save ( void )
        {
            MESSAGE( "Sending process save signal" );
            if ( _pid )
                ::kill( _pid, _save_signal );
        }


    bool dump ( const char *path )
        {
            //Dump current config to file. This happens quite often.
            char *fname;
            asprintf( &fname, "%s/%s", path, CONFIG_FILE_NAME );

            FILE *fp = fopen( fname, "w" );

            free( fname );

            if ( !fp )
            {
                WARNING( "Error opening file for saving: %s", strerror( errno ) );
                return false;
            }

            if ( _executable && strlen(_executable) )
                fprintf( fp, "executable\n\t%s\n", _executable );

            if ( _arguments && strlen(_arguments) )
                fprintf( fp, "arguments\n\t%s\n", _arguments );

            if ( _config_file && strlen(_config_file) )
                fprintf( fp, "config file\n\t%s\n", _config_file );

            fprintf( fp, "save signal\n\t%i\n", _save_signal );

            fprintf( fp, "stop signal\n\t%i\n", _stop_signal );

            if ( _label && strlen(_label) )
                fprintf( fp, "label\n\t%s\n", _label );

            fclose( fp );

            return true;
        }

    bool restore ( const char *path )
        {
            FILE *fp = fopen( path, "r" );
            if ( ! fp )
            {
                WARNING( "Error opening file for restore: %s", strerror( errno ) );
                return false;
            }

            char *name;
            char *value;

            MESSAGE( "Loading file config \"%s\"", path );

            while ( 2 == fscanf( fp, "%m[^\n]\n\t%m[^\n]\n", &name, &value ) )
            {

                MESSAGE( "%s=%s", name, value );

                if ( !strcmp( name, "executable" ) )
                    _executable = value;
                else if (!strcmp( name, "arguments" ) )
                    _arguments = value;
                else if (!strcmp( name, "config file" ) )
                    _config_file = value;
                else if ( !strcmp( name, "save signal" ) )
                {
                    _save_signal = atoi( value );
                    free( value );
                }
                else if ( !strcmp( name, "stop signal" ) )
                {
                    _stop_signal = atoi( value );
                    free( value );
                }
                else if ( !strcmp( name, "label" ) )
                {
                    label( value );
                    free( value );
                }
                else
                {
                    WARNING( "Unknown option \"%s\" in config file", name );
                }

                free( name );
            }

            fclose( fp );

            start();

            return true;
        }

    void update ( lo_address to )
        {
            //Each send triggers one osc_update in the Proxy-GUI.
            MESSAGE( "Sending update" );

            lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/save_signal", "i",  _save_signal );
            lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/label", "s", _label ? _label : "" );
            lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/executable", "s", _executable ? _executable : "" );
            lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/arguments", "s", _arguments ? _arguments : "" );
            lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/config_file", "s", _config_file ? _config_file : "" );
            lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/stop_signal", "i",  _stop_signal );
            lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/proxy/client_error", "s",  _client_error ? _client_error : "" );
        }
};

NSM_Proxy *nsm_proxy;

bool
snapshot ( const char *file )
{
    return nsm_proxy->dump(file);
}
void
announce ( const char *nsm_url, const char *client_name, const char *process_name )
{
    printf( "Announcing to NSM\n" );

    lo_address to = lo_address_new_from_url( nsm_url );

    int pid = (int)getpid();

    lo_send_from( to, losrv, LO_TT_IMMEDIATE, "/nsm/server/announce", "sssiii",
                  client_name,
                  ":optional-gui:",
                  process_name,
                  1, /* api_major_version */
                  0, /* api_minor_version */
                  pid );

    lo_address_free( to );
}

bool
open ( const char *file )
{
    char *path;
    asprintf( &path, "%s/%s", file, CONFIG_FILE_NAME );

    bool r = nsm_proxy->restore( path );

    free( path );

    return r;
}

/****************/
/* OSC HANDLERS */
/****************/

/* NSM */

int
osc_announce_error ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    if ( strcmp( types, "sis" ) )
        return -1;

    if ( strcmp( "/nsm/server/announce", &argv[0]->s ) )
         return -1;

    printf( "Failed to register with NSM: %s\n", &argv[2]->s );
    nsm_is_active = 0;

    return 0;
}


int
osc_announce_reply ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    if ( strcmp( "/nsm/server/announce", &argv[0]->s ) )
         return -1;

    printf( "Successfully registered. NSM says: %s", &argv[1]->s );

    nsm_is_active = 1;
    nsm_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ) );

    return 0;
}

int
osc_save ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    bool r = snapshot( project_file );

    nsm_proxy->save();

    if ( r )
        lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );
    else
        lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/error", "sis", path, -1, "Error saving project file" );

    return 0;
}

static int gui_pid;

void
show_gui ( void )
{

    int pid;
    if ( ! (pid = fork()) )
    {
        char executable[] = "nsm-proxy-gui";

        MESSAGE( "Launching %s\n", executable );

        char *url = lo_server_get_url( losrv );

        char *args[] = { executable, strdup( "--connect-to" ), url, NULL };

        if ( -1 == execvp( executable, args ) )
        {
            WARNING( "Error starting process: %s", strerror( errno ) );

            exit(1);
        }
    }

    gui_pid = pid;

    lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_shown", "" );
}

int
osc_show_gui ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    show_gui();

    /* FIXME: detect errors */

    lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );

    return 0;
}

void
hide_gui ( void )
{
    if ( gui_pid )
    {
        kill( gui_pid, SIGTERM );
    }
}

int
osc_hide_gui ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    hide_gui();

    lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" );

    /* FIXME: detect errors */

    lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );

    return 0;
}

int
osc_open ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    const char *new_path = &argv[0]->s;
    const char *display_name = &argv[1]->s;
    const char *client_id = &argv[2]->s;

    if ( nsm_client_id )
        free(nsm_client_id);

    nsm_client_id = strdup( client_id );

    if ( nsm_display_name )
        free( nsm_display_name );

    nsm_display_name = strdup( display_name );

    char *new_filename;

    mkdir( new_path, 0777 );

    chdir( new_path );

    asprintf( &new_filename, "%s/%s", new_path, CONFIG_FILE_NAME );

    struct stat st;

    if ( 0 == stat( new_filename, &st ) )
    {
        if ( open( new_path ) )
        {
        }
        else
        {
            lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/error", "sis", path, -1, "Could not open file" );
            return 0;
        }

        lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" );
    }
    else
    {
        show_gui();
    }

    if ( project_file )
        free( project_file );

    project_file = strdup( new_path );

// new_filename;

    lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/reply", "ss", path, "OK" );

    if ( gui_addr )
        nsm_proxy->update( gui_addr );

    return 0;
}



/* GUI */

int
osc_label ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    nsm_proxy->label( &argv[0]->s );

    return 0;
}

int
osc_save_signal ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    nsm_proxy->save_signal( argv[0]->i );

    return 0;
}

int
osc_stop_signal ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    nsm_proxy->stop_signal( argv[0]->i );

    return 0;
}

int
osc_start ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    snapshot( project_file );

    if ( nsm_proxy->start( &argv[0]->s, &argv[1]->s, &argv[2]->s ) )
    {
        hide_gui();
    }

    return 0;
}

int
osc_kill ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    nsm_proxy->kill();

    return 0;
}

int
osc_update ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )
{
    lo_address to = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ));

    nsm_proxy->update( to );

    gui_addr = to;

    return 0;
}



void
signal_handler ( int x )
{
    die_now = 1;
}

void
set_traps ( void )
{
    signal( SIGHUP, signal_handler );
    signal( SIGINT, signal_handler );
//  signal( SIGQUIT, signal_handler );
//  signal( SIGSEGV, signal_handler );
//  signal( SIGPIPE, signal_handler );
    signal( SIGTERM, signal_handler );
}


void
init_osc ( const char *osc_port )
{
    losrv = lo_server_new( osc_port, NULL );
//error_handler );

    char *url = lo_server_get_url(losrv);
    printf("OSC: %s\n",url);
    free(url);

    /* NSM */
    lo_server_add_method( losrv, "/nsm/client/save", "", osc_save, NULL );
    lo_server_add_method( losrv, "/nsm/client/open", "sss", osc_open, NULL );
    lo_server_add_method( losrv, "/nsm/client/show_optional_gui", "", osc_show_gui, NULL );
    lo_server_add_method( losrv, "/nsm/client/hide_optional_gui", "", osc_hide_gui, NULL );
    lo_server_add_method( losrv, "/error", "sis", osc_announce_error, NULL );
    lo_server_add_method( losrv, "/reply", "ssss", osc_announce_reply, NULL );

    /* GUI */
    lo_server_add_method( losrv, "/nsm/proxy/label", "s", osc_label, NULL );
    lo_server_add_method( losrv, "/nsm/proxy/save_signal", "i", osc_save_signal, NULL );
    lo_server_add_method( losrv, "/nsm/proxy/stop_signal", "i", osc_stop_signal, NULL );
    lo_server_add_method( losrv, "/nsm/proxy/kill", "", osc_kill, NULL );
    lo_server_add_method( losrv, "/nsm/proxy/start", "sss", osc_start, NULL );
    lo_server_add_method( losrv, "/nsm/proxy/update", "", osc_update, NULL );

}

void
die ( void )
{
    if ( gui_pid )
    {
        MESSAGE( "Killing GUI" );

        kill( gui_pid, SIGTERM );
    }

    nsm_proxy->kill();

    exit(0);
}


void handle_sigchld ( )
{
    for ( ;; )
    {
        int status;
        pid_t pid = waitpid(-1, &status, WNOHANG);

        if (pid <= 0)
            break;

        if ( pid == gui_pid )
        {
            lo_send_from( nsm_addr, losrv, LO_TT_IMMEDIATE, "/nsm/client/gui_is_hidden", "" );

            gui_pid = 0;

            /* don't care... */
            continue;
        }

        if ( WIFSIGNALED(status)  )
        {
            /* process was killed via signal */
            if (WTERMSIG(status) == SIGTERM ||
                WTERMSIG(status) == SIGHUP ||
                WTERMSIG(status) == SIGINT ||
                WTERMSIG(status) == SIGKILL )
            {
                /* process was killed via an appropriate signal */
                MESSAGE( "child was killed (maybe by us)\n" );
                die_now = 1;
                continue;
            }
        }
        else if ( WIFEXITED(status) )
        {
            /* child called exit() or returned from main() */

            MESSAGE( "child exit status: %i", WEXITSTATUS(status) );

            if ( WEXITSTATUS(status) == 0 )
            {
                /* apparently normal termination */
                MESSAGE( "child exited without error.");
                die_now = 1;
                continue;
            }
            else
            {
                MESSAGE("child exited abnormally.");
                nsm_proxy->handle_client_death(WEXITSTATUS(status));
            }
        }
    }
}

int
main ( int argc, char **argv )
{
    set_traps();

    sigset_t mask;

    sigemptyset( &mask );
    sigaddset( &mask, SIGCHLD );

    sigprocmask(SIG_BLOCK, &mask, NULL );

    signal_fd = signalfd( -1, &mask, SFD_NONBLOCK );

    //Command line parameters
    static struct option long_options[] =
    {
        { "help", no_argument, 0, 'h' },
        { 0, 0, 0, 0 }
    };
    int option_index = 0;
    int c = 0;
    while ( ( c = getopt_long_only( argc, argv, "", long_options, &option_index  ) ) != -1 )
    {
        switch ( c )
        {
            case 'h':
                {
                const char *usage =
                "nsm-proxy - Wrapper for executables without direct NSM-Support.\n\n"
                "It is a module for the 'New Session Manager' and only communicates\n"
                "over OSC in an NSM-Session and has no standalone functionality.\n"
                "\n"
                "Usage:\n"
                "  nsm-proxy --help\n"
                "\n"
                "Options:\n"
                "  --help                Show this screen\n"
                "";
                puts ( usage );
                exit(0);
                break;
                }
        }
    }



    nsm_proxy = new NSM_Proxy();

    init_osc( NULL );

    const char *nsm_url = getenv( "NSM_URL" );

    if ( nsm_url )
    {
        announce( nsm_url, APP_TITLE, argv[0] );
    }
    else
    {
        fprintf( stderr, "Could not register as NSM client.\n" );
        exit(1);
    }


    struct signalfd_siginfo fdsi;

    /* listen for sigchld signals and process OSC messages forever */
    for ( ;; )
    {
        ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo));

        if (s == sizeof(struct signalfd_siginfo))
        {
            if (fdsi.ssi_signo == SIGCHLD)
                handle_sigchld();
        }

        lo_server_recv_noblock( losrv, 500 );

        if ( die_now )
            die();
    }
}
07070100000046000081A40000000000000000000000016258A65C00002546000000000000000000000000000000000000004100000000new-session-manager-1.6.0+git.20220415.0f6719c/src/nsm-proxy.svg<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="68.791656mm"
   height="68.791656mm"
   viewBox="0 0 68.791656 68.791656"
   version="1.1"
   id="svg8"
   sodipodi:docname="icons.svg"
   inkscape:version="1.0 (4035a4fb49, 2020-05-01)">
  <defs
     id="defs2" />
  <sodipodi:namedview
     units="px"
     id="base"
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0.0"
     inkscape:pageshadow="2"
     inkscape:zoom="0.7"
     inkscape:cx="-626.55231"
     inkscape:cy="350.73591"
     inkscape:document-units="mm"
     inkscape:current-layer="layer1"
     inkscape:document-rotation="0"
     showgrid="false"
     inkscape:window-width="2558"
     inkscape:window-height="1398"
     inkscape:window-x="0"
     inkscape:window-y="20"
     inkscape:window-maximized="1" />
  <metadata
     id="metadata5">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     transform="translate(-231.11632,-36.693907)"
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1">
    <g
       id="nsm"
       inkscape:label="nsm"
       transform="translate(-172.56864)">
      <title
         id="title888">nsm</title>
      <rect
         y="114.63333"
         x="71.133331"
         height="67.73333"
         width="67.73333"
         id="rect10"
         style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
      <text
         xml:space="preserve"
         style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
         x="105.32493"
         y="169.25755"
         id="text852"><tspan
           sodipodi:role="line"
           x="105.32493"
           y="169.25755"
           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
           id="tspan850">NSM</tspan></text>
    </g>
    <g
       transform="translate(-170.13191,80.919145)"
       inkscape:label="jackpatch"
       id="jackpatch">
      <title
         id="title890">jackpatch</title>
      <rect
         style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
         id="rect892"
         width="67.73333"
         height="67.73333"
         x="71.133331"
         y="114.63333" />
      <text
         id="text896"
         y="165.8237"
         x="106.38719"
         style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
         xml:space="preserve"><tspan
           id="tspan894"
           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
           y="165.8237"
           x="106.38719"
           sodipodi:role="line">JP</tspan></text>
    </g>
    <g
       id="proxy"
       inkscape:label="proxy">
      <title
         id="title940">proxy</title>
      <rect
         y="37.223072"
         x="-99.065941"
         height="67.73333"
         width="67.73333"
         id="rect902"
         style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
      <text
         xml:space="preserve"
         style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
         x="-65.639175"
         y="93.759422"
         id="text906"><tspan
           sodipodi:role="line"
           x="-65.639175"
           y="93.759422"
           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
           id="tspan904">PRX</tspan></text>
    </g>
    <path
       d="m 231.64549,37.223072 h 67.73333 V 104.9564 h -67.73333 z"
       style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
       id="rect1679" />
    <g
       style="font-style:normal;font-weight:normal;font-size:67.6707px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
       id="text1683"
       aria-label="PRX">
      <path
         id="path1690"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 238.98532,48.420053 0.0677,45.339369 h 4.87229 V 72.578493 l 0.27069,-0.06767 h 4.60161 c 1.42108,0 2.63915,-0.406024 3.65421,-1.150401 1.01506,-0.744378 1.48876,-1.962451 1.48876,-3.654218 V 53.427685 c 0,-1.96245 -0.4737,-3.315864 -1.42109,-3.992571 -0.94739,-0.676707 -2.16546,-1.015061 -3.72188,-1.015061 z m 4.93996,4.872291 h 3.31587 c 0.47369,0 0.81205,0.135341 1.1504,0.473695 0.33835,0.338353 0.4737,0.744377 0.4737,1.218072 v 10.962654 c 0,0.473694 -0.13535,0.812048 -0.4737,1.150401 -0.33835,0.338354 -0.67671,0.473695 -1.1504,0.473695 h -3.31587 z" />
      <path
         id="path1692"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 262.66997,53.292344 h 3.31586 c 0.4737,0 0.81205,0.135341 1.15041,0.473695 0.33835,0.338353 0.47369,0.744377 0.47369,1.218072 v 10.962654 c 0,0.473694 -0.13534,0.812048 -0.47369,1.150401 -0.33836,0.338354 -0.67671,0.473695 -1.15041,0.473695 h -3.31586 z m -4.93996,-4.872291 0.0677,45.339369 H 262.67 v -21.2486 h 1.21807 l 4.19559,21.2486 h 4.87229 L 268.69266,72.24014 c 0.60904,-0.203012 1.42109,-0.609037 2.50382,-1.218073 0.94739,-0.609036 1.48875,-1.759438 1.48875,-3.451206 V 53.427685 c 0,-1.285743 -0.47369,-2.436145 -1.48875,-3.518876 -1.01506,-1.015061 -2.16547,-1.488756 -3.51888,-1.488756 z" />
      <path
         id="path1694"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;text-anchor:middle;stroke-width:1.45009"
         d="m 275.6627,48.420053 5.0753,23.346392 -4.80462,21.789965 h 5.00763 l 2.90984,-13.53414 2.90984,13.53414 h 4.93996 l -4.73695,-21.789965 5.07531,-23.346392 h -5.14298 l -3.04518,14.752213 -3.04518,-14.752213 z" />
    </g>
    <path
       d="m 230.75774,195.55247 h 67.73333 v 67.73333 h -67.73333 z"
       style="fill:#ffffff;stroke:#000000;stroke-width:1.05833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
       id="rect1700" />
    <path
       d="m 256.70029,252.35951 v -50.95604 h -5.0753 v 49.46729 c 0,0.47369 -0.13534,0.81204 -0.4737,1.1504 -0.33835,0.33835 -0.6767,0.47369 -1.1504,0.47369 h -1.75944 v 4.93996 h 3.04518 c 3.58655,0 5.41366,-1.69176 5.41366,-5.0753 z"
       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;line-height:1.25;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
       id="path1712" />
    <path
       d="m 266.05212,201.40347 0.0677,45.33937 h 4.87229 v -21.18093 l 0.27068,-0.0677 h 4.60161 c 1.42108,0 2.63916,-0.40602 3.65422,-1.1504 1.01506,-0.74438 1.48875,-1.96245 1.48875,-3.65422 v -14.27851 c 0,-1.96245 -0.47369,-3.31587 -1.42108,-3.99257 -0.94739,-0.67671 -2.16546,-1.01507 -3.72189,-1.01507 z m 4.93996,4.87229 h 3.31586 c 0.4737,0 0.81205,0.13535 1.1504,0.4737 0.33836,0.33835 0.4737,0.74438 0.4737,1.21807 v 10.96266 c 0,0.47369 -0.13534,0.81204 -0.4737,1.1504 -0.33835,0.33835 -0.6767,0.47369 -1.1504,0.47369 h -3.31586 z"
       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:67.6707px;line-height:1.25;font-family:'Sturkopf Grotesk';-inkscape-font-specification:'Sturkopf Grotesk';text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.45009"
       id="path1714" />
  </g>
</svg>
07070100000047000081A40000000000000000000000016258A65C000149A7000000000000000000000000000000000000003C00000000new-session-manager-1.6.0+git.20220415.0f6719c/src/nsmd.cpp
/*******************************************************************************/
/* Copyright (C) 2008-2020 Jonathan Moore Liles (as "Non-Session-Manager")     */
/* Copyright (C) 2020- Nils Hilbricht                                          */
/*                                                                             */
/* This file is part of New-Session-Manager                                    */
/*                                                                             */
/* New-Session-Manager 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.                                         */
/*                                                                             */
/* New-Session-Manager 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 New-Session-Manager. If not, see <https://www.gnu.org/licenses/>.*/
/*******************************************************************************/

#define __MODULE__ "nsmd"

//debug.c has only one function that gets used multiple times by debug.h and for logging and printing
#include "debug.h"

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <errno.h>
#include <string.h>
#include <list>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/signalfd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <libgen.h>
#include <list>
#include <getopt.h>
#include <sys/time.h>
#include <fts.h>

#include "Endpoint.hpp"
/* for locking */
#include "file.h"

#include <map>
#include <string>
#include <algorithm>

#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-result"

static OSC::Endpoint *osc_server;
static lo_address gui_addr;
static bool gui_is_active = false;
static int signal_fd;

static char *session_root;
static char *lockfile_directory;
static char *daemon_file;

#define NSM_API_VERSION_MAJOR 1
#define NSM_API_VERSION_MINOR 1
#define NSM_API_VERSION_PATCH 2
#define VERSION_STRING "1.6.0"

#define ERR_OK 0
#define ERR_GENERAL_ERROR    -1
#define ERR_INCOMPATIBLE_API -2
#define ERR_BLACKLISTED      -3
#define ERR_LAUNCH_FAILED    -4
#define ERR_NO_SUCH_FILE     -5
#define ERR_NO_SESSION_OPEN  -6
#define ERR_UNSAVED_CHANGES  -7
#define ERR_NOT_NOW          -8
#define ERR_BAD_PROJECT      -9
#define ERR_CREATE_FAILED    -10
#define ERR_SESSION_LOCKED   -11
#define ERR_OPERATION_PENDING -12

#define APP_TITLE "New Session Manager"

enum {
    COMMAND_NONE = 0,
    COMMAND_QUIT,
    COMMAND_KILL,
    COMMAND_SAVE,
    COMMAND_OPEN,
    COMMAND_START,

    COMMAND_CLOSE,
    COMMAND_DUPLICATE,
    COMMAND_NEW
};

static int pending_operation = COMMAND_NONE;

static void wait ( long );

void handle_signal_clean_exit ( int );


#define GUIMSG( fmt, args... ) \
{ \
    MESSAGE( fmt, ## args ); \
    if ( gui_is_active ) \
    { \
        char *s;\
        asprintf( &s, fmt, ## args );\
        osc_server->send( gui_addr, "/nsm/gui/server/message", s);\
        free(s);\
    }\
}

struct Client
{
private:

    int _reply_errcode;
    char *_reply_message;

    int _pending_command;
    struct timeval _command_sent_time;

    bool _gui_visible;

    char *_label;

public:

    lo_address addr=0;                 /*  */
    char *name;                        /* First this is the basename of client executable, later it becomes the client-reported name which must be treated as if unrelated. */
    char *executable_path;             /* Contrary to the name this is basename(executable) */
    int pid;                           /* PID of client process */
    float progress;                    /*  */
    bool active;                       /* NSM capable: client has registered via announce */
    //bool stopped;                    /* the client quit, but not because we told it to--user still has to decide to remove it from the session */
    char *client_id;                   /* short part of client ID */
    char *capabilities;                /* client capabilities... will be null for dumb clients */
    bool dirty;                        /* flag for client self-reported dirtiness */
    bool pre_existing;
    const char *status;
    int launch_error;                   /* v1.4, leads to status for executable not found, permission denied etc. */
    char *name_with_id;                  /* v1.4, client.nABC */

    const char *label ( void ) const { return _label; }
    void label ( const char *l )
        {
            if ( _label )
                free( _label );
            if ( l )
                _label = strdup( l );
            else
                _label = NULL;
        }

    bool gui_visible ( void ) const
        {
            return _gui_visible;
        }

    void gui_visible ( bool b )
        {
            _gui_visible = b;
        }

    bool
    has_error ( void ) const
        {
            return _reply_errcode != 0;
        }

    int
    error_code ( void ) const
        {
            return _reply_errcode;
        }

    const char * message ( void )
        {
            return _reply_message;
        }

    void
    set_reply ( int errcode, const char *message )
        {
            if ( _reply_message )
                free( _reply_message );

            _reply_message = strdup( message );
            _reply_errcode = errcode;
        }

    bool reply_pending ( void )
        {
            return _pending_command != COMMAND_NONE;
        }

    bool is_dumb_client ( void )
        {
            return capabilities == NULL;
        }

    void pending_command ( int command )
        {
            gettimeofday( &_command_sent_time, NULL );
            _pending_command = command;
        }

    double milliseconds_since_last_command ( void ) const
        {
            struct timeval now;

            gettimeofday( &now, NULL );

            double elapsedms = ( now.tv_sec - _command_sent_time.tv_sec ) * 1000.0;
            elapsedms += ( now.tv_usec - _command_sent_time.tv_usec ) / 1000.0;

            return elapsedms;
        }

    int pending_command ( void )
        {
            return _pending_command;
        }

// capability should be enclosed in colons. I.e. ":switch:"
    bool
    is_capable_of ( const char *capability ) const
        {
            return capabilities &&
                strstr( capabilities, capability );
        }

    Client ( )
        {
            _label = 0;
            _gui_visible = true;
            _reply_errcode = 0;
            _reply_message = 0;
            pid = 0;
            progress = -0;
            _pending_command = 0;
            active = false;
            client_id = 0;
            capabilities = 0;
            name = 0;
            executable_path = 0;
            pre_existing = false;
            launch_error = 0;
            dirty = 0;
            status = 0;
            name_with_id = 0;

        }

    ~Client ( )
        {
            if ( name )
                free(name);
            if (executable_path)
                free(executable_path);
            if (client_id)
                free(client_id);
            if (capabilities)
                free(capabilities);
            if (name_with_id)
                free(name_with_id);

            name = executable_path = client_id = capabilities = name_with_id = NULL;
        }
};


static std::list< Client* > client;

/* helper macros for defining OSC handlers */
#define OSC_NAME( name ) osc_ ## name
#define OSC_HANDLER( name ) static int OSC_NAME( name ) ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data )

static char *session_path = NULL;
static char *session_name = NULL;

bool
clients_have_errors ( )
{
    for ( std::list<Client*>::const_iterator i = client.begin();
          i != client.end();
          ++i )
        if ( (*i)->active && (*i)->has_error() )
            return true;

    return false;
}

Client *
get_client_by_pid ( int pid )
{
    std::list<Client*> *cl = &client;

    for ( std::list<Client*>::const_iterator i = cl->begin();
          i != cl->end();
          ++i )
        if ( (*i)->pid == pid )
            return *i;

    return NULL;
}

void clear_clients ( void )
{
    std::list<Client*> *cl = &client;

    for ( std::list<Client*>::iterator i = cl->begin();
          i != cl->end();
          ++i )
    {
        delete *i;
        i = cl->erase( i );
    }
}


void
handle_client_process_death ( int pid )
{
    Client *c = get_client_by_pid( (int)pid );

    if ( c )
    {
        //There is a difference if a client quit on its own, e.g. via a menu or window manager,
        //or if the server send SIGTERM as quit signal. Both cases are equally valid.
        //We only check the case to print a different log message
        bool dead_because_we_said = ( c->pending_command() == COMMAND_KILL ||
                                      c->pending_command() == COMMAND_QUIT );

        if ( dead_because_we_said )
        {
            GUIMSG( "Client %s terminated by server instruction.", c->name_with_id );
        }
        else
        {
            GUIMSG( "Client %s terminated itself.", c->name_with_id );
        }


        //Decide if the client terminated or if removed from the session
        if ( c->pending_command() == COMMAND_QUIT )
        {
            c->status = "removed";
            if ( gui_is_active )
                osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );

            client.remove(c); //This will not remove the clients save data
            delete c;
        }
        else
        {
            if ( c->launch_error )
               /* NSM API treats the stopped status as switch. You can only remove stopped.
                * Furthermore the GUI will change its client-buttons.
                * In consequence we cannot add an arbitrary "launch-error" status.
                * Compatible compromise is to use the label field to relay info the user,
                * which was the goal. There is nothing we can do about a failed launch anyway.
                */
                c->label( "launch error!" );
            else
                c->label( "" );

            c->status = "stopped";

            if ( gui_is_active )
            {
                osc_server->send( gui_addr, "/nsm/gui/client/label", c->client_id, c->label() );
                osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );
            }
        }

        c->pending_command( COMMAND_NONE );

        c->active = false;
        c->pid = 0;
     }
}

void handle_sigchld ( )
{
    // compare waitpid(2)
    for ( ;; )
    {
        int status = 1; // make it not NULL to enable information storage in status
        pid_t pid = waitpid(-1, &status, WNOHANG); //-1 meaning wait for any child process. pid_t is signed integer

        if (pid <= 0)
        {
            break; // no child process has ended this loop. Check again.
        }
        else
        {
            //One child process has stopped. Find which and figure out the stop-conditions
            Client *c;
            c = get_client_by_pid( pid );
            if ( c )
            {
                //The following will not trigger with normal crashes, e.g. segfaults or python tracebacks
                if ( WIFEXITED( status ) ) // returns true if the child terminated normally
                    if ( WEXITSTATUS( status ) == 255 ) // as given by exit(-1) in launch()
                        c->launch_error = true;
            }
            // Call even if Client was already null. This will check itself again and was expected
            // to be called for the majority of nsmds development
            handle_client_process_death( pid );
        }
    }
}


int
path_is_valid ( const char *path )
{
    char *s;

    asprintf( &s, "/%s/", path );

    int r = strstr( s, "/../" ) == NULL;

    free( s );

    return r;
}

int
session_already_exists ( const char * relative_session_path)
{
    //A session is defined as a path with the file session.nsm
    //We receive the relative path with sub-directories like album/song as relative_session_path, without leading and trailing /
    struct stat st_session_exists_check;
    char * path;
    asprintf( &path, "%s/%s/session.nsm", session_root, relative_session_path );
    if ( stat (path, &st_session_exists_check) == 0) {
        free( path );
        return 0; // Already exists
    }
    else {
        free( path );
        return -1; // All good
    }
}

int
mkpath ( const char *path, bool create_final_directory )
{
    char *p = strdup( path );

    char *i = p + 1;

    while ( ( i = index( i, '/' ) ) )
    {
        *i = 0;

        struct stat st;

        if ( stat( p, &st ) )
        {
            if ( mkdir( p, 0711 ) )
            {
                free( p );
                return -1;
            }
        }

        *i = '/';
        i++;
    }

    if ( create_final_directory )
    {
        if ( mkdir( p, 0711 ) )
        {
            free( p );
            return -1;
        }
    }

    free( p );

    return 0;
}

void
set_name ( const char *name )
{
    if ( session_name )
        free( session_name );

    char *s = strdup( name );

    session_name = strdup( basename( s ) );

    free( s );
}

bool
address_matches ( lo_address addr1, lo_address addr2 )
{
    /* char *url1 = lo_address_get_url( addr1 ); */
    /* char *url2 = lo_address_get_url( addr2 ); */

    char *url1 = strdup( lo_address_get_port( addr1 ) );
    char *url2 = strdup(lo_address_get_port( addr2 ) );

    bool r = !strcmp( url1, url2 );

    free( url1 );
    free( url2 );

    return r;
}

Client *
get_client_by_id ( std::list<Client*> *cl, const char *id )
{
    for ( std::list<Client*>::const_iterator i = cl->begin();
          i != cl->end();
          ++i )
        if ( !strcmp( (*i)->client_id, id ) )
            return *i;

    return NULL;
}


Client *
get_client_by_name_and_id ( std::list<Client*> *cl, const char *name, const char *id )
{
    for ( std::list<Client*>::const_iterator i = cl->begin();
          i != cl->end();
          ++i )
        if ( !strcmp( (*i)->client_id, id ) &&
             ! strcmp( (*i)->name, name ) )
            return *i;

    return NULL;
}

Client *
get_client_by_address ( lo_address addr )
{
    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
        if ( (*i)->addr && address_matches( (*i)->addr, addr ) )
            return *i;

    return NULL;
}


char *
generate_client_id ( void )
{
    /* Before v1.4 this returned "n" + 4 random upper-case letters, which could lead to collisions.
    We changed behaviour to still generate 4 letters, but check for collision with existing IDs.

    Loaded client IDs are not checked, just copied from session.nsm because loading happens before
    any generation of new clients. Loaded clients are part of further checks of course.

    There is a theoretical limit when all 26^4 IDs are in use which will lead to an infinite loop
    of generation. We risk to leave this unhandled. */

    char id_str[6];

    id_str[0] = 'n';
    id_str[5] = 0;

    while ( true )
    {
        for ( int i = 1; i < 5; i++ )
            id_str[i] = 'A' + (rand() % 25);

        if ( get_client_by_id(&client, id_str )==NULL ) // found a free id
            break;
    }

    return strdup(id_str);
}


bool
replies_still_pending ( void )
{
    for ( std::list<Client*>::const_iterator i = client.begin();
          i != client.end();
          ++i )
        if ( (*i)->active && (*i)->reply_pending() )
            return true;

    return false;
}

int
number_of_reponsive_clients ( void )
{
    /* This was renamed from number_of_active_clients in version 1.4 to reflect
     * that not only active==true clients are in a state where waiting has ended, but also clients
     * that never started. It is used in wait_for_announce only, which added a 5000ms delay to startup
     *
     * We are sadly unable to distinguish between a client that has a slow announce and a client
     * without NSM-support. However, this is mitigated by nsm-proxy which is a reliable indicator
     * that this program will never announce (or rather nsm-proxy announces normally).
     */

    int responsive = 0;
    for ( std::list<Client*>::const_iterator i = client.begin(); i != client.end(); ++i )
    {
        //Optimisation: Clients that never launched (e.g. file not found) will be checked many times/seconds here. We skip them by counting them
        if ( (*i)->active || (*i)->launch_error )
            responsive++;
    }

    return responsive;
}

void
wait_for_announce ( void )
{
    GUIMSG( "Waiting for announce messages from clients" );

    int n = 5 * 1000;

    long unsigned int active;

    while ( n > 0 )
    {
        n -= 100;

        wait(100);

        active = number_of_reponsive_clients();

        if ( client.size() == active )
            break;
    }

    GUIMSG( "Done. %lu out of %lu clients announced (or failed to launch) within the initialization grace period",
            active, (long unsigned)client.size() );
}

void
wait_for_replies ( void )
{

    GUIMSG( "Waiting for clients to reply to commands" );

    int n = 60 * 1000;                                          /* 60 seconds */

    while ( n )
    {
        n -= 100;

        wait(100);

        if ( ! replies_still_pending() )
            break;
    }

    GUIMSG( "Done waiting" );
    /* FIXME: do something about unresponsive clients */
}


char *
get_client_project_path  ( const char *session_path, Client *c )
{
    char *client_project_path;

    asprintf( &client_project_path, "%s/%s.%s", session_path, c->name, c->client_id );

    return client_project_path;
}


bool
launch ( const char *executable, const char *client_id )
{
    Client *c;

    if ( !client_id || !( c = get_client_by_id( &client, client_id ) ) )
    {
        c = new Client();

        c->executable_path = strdup( executable );

        {
            char *s = strdup( c->executable_path );

            c->name = strdup( basename( s ) );

            free( s );
        }

        if ( client_id )
            c->client_id = strdup( client_id );
        else
            c->client_id = generate_client_id();

        asprintf( &c->name_with_id, "%s.%s", c->name, c->client_id );

        client.push_back( c );
    }

    char * url = osc_server->url();

    int pid;
    if ( ! (pid = fork()) )
    {
        //This is code of the child process. It will be executed after launch() has finished
        GUIMSG( "Launching %s", executable );

        char *args[] = { strdup( executable ), NULL };

        setenv( "NSM_URL", url, 1 );

        /* Ensure the launched process can receive SIGCHLD */
        /* Unblocking SIGCHLD here does NOT unblock it for nsmd itself */
        sigset_t mask;
        sigemptyset( &mask );
        sigaddset( &mask, SIGCHLD );
        sigprocmask(SIG_UNBLOCK, &mask, NULL );

        if ( -1 == execvp( executable, args ) )
        {
            /* The program was not started. Causes: not installed on the current system, and the
             * session was transferred from another system, or permission denied (no executable flag)
             * Since we are running in a forked child process Client c does exist, but points to
             * a memory copy, not the real client. So we can't set any error code or status in the
             * client object. Instead we check the exit return code in handle_sigchld() and set the
            *  bool client->launch_error to true.
            */

            WARNING( "Error starting process %s: %s", executable, strerror( errno ) );
            exit(-1); //-1 later parsed as 255
        }
    }

    //This is code of the parent process. It is executed right at this point, before the child.
    c->pending_command( COMMAND_START );
    c->pid = pid;

    MESSAGE( "Process %s has pid: %i", executable, pid ); //We do not have a name yet, use executable

    //Normal launch. Setting launch_error to false is not redundant:
    //A previous launch-error fixed by the user, and then resume, needs this reset.
    c->launch_error = false;
    c->status = "launch";
    if ( gui_is_active )
    {
        //At this point we do not know if launched program will start or fail
        //And we do not know if it has nsm-support or not. This will be decided if it announces.
        osc_server->send( gui_addr, "/nsm/gui/client/new", c->client_id, c->executable_path ); // a second message may get send with c->name, if the client sends announce()
        osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );
        osc_server->send( gui_addr, "/nsm/gui/client/label", c->client_id, "" );
    }

    return true;
}

void
command_client_to_save ( Client *c )
{
    if ( c->active )
    {
        MESSAGE( "Telling %s to save", c->name_with_id );
        osc_server->send( c->addr, "/nsm/client/save" );

        c->pending_command( COMMAND_SAVE );

        c->status = "save";
        if ( gui_is_active )
            osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id,  c->status );
    }
    else if ( c->is_dumb_client() && c->pid )
    {
        // this is a dumb client...
        c->status = "noop";
        if ( gui_is_active )
            osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );
    }
}

void command_client_to_switch ( Client *c, const char *new_client_id )
{
    char *old_client_id = c->client_id;

    c->client_id = strdup( new_client_id );

    char *client_project_path = get_client_project_path( session_path, c );

    MESSAGE( "Commanding %s to switch \"%s\"", c->name_with_id, client_project_path );

    char *full_client_id;
    asprintf( &full_client_id, "%s.%s", c->name, c->client_id );

    osc_server->send( c->addr, "/nsm/client/open", client_project_path, session_name, full_client_id );

    free( full_client_id );
    free( client_project_path );

    c->pending_command( COMMAND_OPEN );

    c->status = "switch";
    if ( gui_is_active )
    {
        osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );
        osc_server->send( gui_addr, "/nsm/gui/client/switch", old_client_id, c->client_id );
    }

    free( old_client_id );
}

void
purge_inactive_clients ( )
{
    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        if ( ! (*i)->active )
        {
            (*i)->status = "removed";
            if ( gui_is_active )
                osc_server->send( gui_addr, "/nsm/gui/client/status", (*i)->client_id, (*i)->status );

            delete *i;

            i = client.erase( i );
        }
    }
}

bool
process_is_running ( int pid )
{
    if ( 0 == kill( pid, 0 ) )
    {
        return true;
    }
    else if ( ESRCH == errno )
    {
        return false;
    }

    return false;
}

void
purge_dead_clients ( )
{
    std::list<Client*> tmp( client );

    for ( std::list<Client*>::const_iterator i = tmp.begin();
          i != tmp.end();
          ++i )
    {
        const Client *c = *i;
        if ( c->pid )
        {
            if ( ! process_is_running( c->pid ) )
                handle_client_process_death( c->pid );
        }
    }
}

/************************/
/* OSC Message Handlers */
/************************/

OSC_HANDLER( add )
{
    if ( ! session_path )
    {
        osc_server->send( lo_message_get_source( msg ), "/error", path,
                          ERR_NO_SESSION_OPEN,
                          "Cannot add to session because no session is loaded." );


        return 0;
    }

    if ( strchr( &argv[0]->s, '/' ) )
    {
        osc_server->send( lo_message_get_source( msg ), "/error", path,
                          ERR_LAUNCH_FAILED,
                          "Absolute paths are not permitted. Clients must be in $PATH" );
        return 0;
    }

    if ( ! launch( &argv[0]->s, NULL ) )
    {
        osc_server->send( lo_message_get_source( msg ), "/error", path,
                          ERR_LAUNCH_FAILED,
                          "Failed to launch process!" );
    }
    else
    {
        osc_server->send( lo_message_get_source( msg ), "/reply", path, "Launched." );
    }

    return 0;
}

OSC_HANDLER( announce )
{
    /* A client announces itself which identifies it as real nsm-capable client, internally represented by the c->active bool.

    If nsmd started the client itself (e.g. through a GUI) at this point the program is already
    part of the session and registered with c->name=basename(executable). For these clients a
    second client/new message is sent, indicating an upgrade of the formerly dumb client. Through
    this c->name changes from executable to the self-reported client name from this announce
    message.

    Before v1.4 clients that announce themselves (started with NSM URL ENV present) never triggered
    the first client/new which sends an executable. This created a problem with attaching GUIs to a
    running nsmd never were able to infer any data from executables, like icons. Changed so that
    every new client scenario sends basename(executable) first.
    */

    const char *client_name = &argv[0]->s;
    const char *capabilities = &argv[1]->s;
    const char *executable_path = &argv[2]->s;
    int major = argv[3]->i;
    int minor = argv[4]->i;
    int pid = argv[5]->i;

    GUIMSG( "Got announce from %s", client_name );

    if ( ! session_path )
    {
        osc_server->send( lo_message_get_source( msg ), "/error",
                          path,
                          ERR_NO_SESSION_OPEN,
                          "Sorry, but there's no session open for this application to join." );
        return 0;
    }

    bool expected_client = false;

    Client *c = NULL;

    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        if ( ! strcmp( (*i)->executable_path, executable_path ) &&
             ! (*i)->active &&
             (*i)->pending_command() == COMMAND_START )
        {
            // I think we've found the slot we were looking for.
            MESSAGE( "Client %s was expected.", (*i)->name );
            c = *i;
            break;
        }
    }

    if ( ! c )
    {
        c = new Client();
        c->executable_path = strdup( executable_path );
        c->client_id = generate_client_id();
    }
    else
        expected_client = true;

    if ( major > NSM_API_VERSION_MAJOR )
    {
        MESSAGE( "Client %s is using incompatible and more recent API version %i.%i", c->name_with_id, major, minor );

        osc_server->send( lo_message_get_source( msg ), "/error",
                          path,
                          ERR_INCOMPATIBLE_API,
                          "Server is using an incompatible API version." );

        return 0;
    }

    c->pid = pid;
    c->capabilities = strdup( capabilities );
    c->addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ));
    c->name = strdup( client_name ); //replace executable with clients self-reported pretty name
    c->active = true;

    asprintf( &c->name_with_id, "%s.%s", c->name, c->client_id );


    MESSAGE( "Process %s has pid: %i", c->name_with_id, pid );

    if ( ! expected_client )
        client.push_back( c );

    MESSAGE( "The client \"%s\" at \"%s\" informs us it's ready to receive commands.", &argv[0]->s, lo_address_get_url( c->addr ) );

    osc_server->send( lo_message_get_source( msg ), "/reply",
                      path,
                      expected_client ?
                      "Acknowledged as full NSM client (started ourselves)." :
                      "Acknowledged as full NSM client (registered itself from the outside).",
                      APP_TITLE,
                      ":server-control:broadcast:optional-gui:" );

    c->status = "open";
    if ( gui_is_active )
    {
        osc_server->send( gui_addr, "/nsm/gui/client/new", c->client_id, c->name ); //pretty-name. not exectuable
        osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );

        if ( c->is_capable_of( ":optional-gui:" ) )
            osc_server->send( gui_addr, "/nsm/gui/client/has_optional_gui", c->client_id );
    }

    {
        char *full_client_id;
        asprintf( &full_client_id, "%s.%s", c->name, c->client_id );

        char *client_project_path = get_client_project_path( session_path, c );

        osc_server->send( lo_message_get_source( msg ), "/nsm/client/open", client_project_path, session_name, full_client_id );

        c->pending_command( COMMAND_OPEN );

        free( full_client_id );
        free( client_project_path );
    }

    return 0;
}

int
save_session_file ( )
{
    char *session_file = NULL;
    asprintf( &session_file, "%s/session.nsm", session_path );

    FILE *fp = fopen( session_file, "w" );

    if ( fp == NULL ) {
        WARNING( "No write access to %s with error: %s.", session_file, strerror( errno ) );
        free( session_file );
        //No need to fclose because fp is null and was never opened.
        return 1; //error
    }

    free( session_file );

    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        fprintf( fp, "%s:%s:%s\n", (*i)->name, (*i)->executable_path, (*i)->client_id );
    }

    fclose( fp );
    return 0; //all ok
}

Client *
client_by_name ( const char *name,
                 std::list<Client*> *cl )
{
    for ( std::list<Client*>::iterator i = cl->begin();
          i != cl->end();
          ++i )
    {
        if ( !strcmp( name, (*i)->name ) )
            return *i;
    }

    return NULL;
}

bool
dumb_clients_are_alive ( )
{
    std::list<Client*> *cl = &client;

    for ( std::list<Client*>::iterator i = cl->begin();
          i != cl->end();
          ++i )
    {
        if ( (*i)->is_dumb_client() && (*i)->pid > 0 )
             {
                MESSAGE( "Waiting for %s", (*i)->name_with_id ); //This replaced the Loop 1, Loop 2 ... 60 message from wait_for_dumb_clients_to_die where you couldn't see which client actually was hanging
                return true;
              }
    }

    return false;
}

void
wait_for_dumb_clients_to_die ( )
{
    struct signalfd_siginfo fdsi;

    GUIMSG( "Waiting for any dumb clients to die." );

    for ( int i = 0; i < 6; i++ )
    {
        if ( ! dumb_clients_are_alive() )
            break;

        ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo));

        if (s == sizeof(struct signalfd_siginfo))
        {
            if (fdsi.ssi_signo == SIGCHLD)
                handle_sigchld();
        }

        usleep( 50000 );
    }

    GUIMSG( "Done waiting" );

    /* FIXME: give up on remaining clients and purge them */
}


bool
killed_clients_are_alive ( )
{
    std::list<Client*> *cl = &client;

    for ( std::list<Client*>::iterator i = cl->begin();
          i != cl->end();
          ++i )
    {
        if ( ( (*i)->pending_command() == COMMAND_QUIT ||
               (*i)->pending_command() == COMMAND_KILL ) &&
             (*i)->pid > 0 )
             {
                MESSAGE( "Waiting for %s", (*i)->name_with_id ); //This replaced the Loop 1, Loop 2 ... 60 message from wait_for_killed_clients_to_die where you couldn't see which client actually was hanging
                return true;
              }
    }

    return false;
}

void
wait_for_killed_clients_to_die ( )
{
    struct signalfd_siginfo fdsi;

    MESSAGE( "Waiting for killed clients to die." );

    for ( int i = 0; i < 60; i++ )
    {
        if ( ! killed_clients_are_alive() )
            goto done;

        ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo));

        if (s == sizeof(struct signalfd_siginfo))
        {
            if (fdsi.ssi_signo == SIGCHLD)
                handle_sigchld();
        }

        purge_dead_clients();

        /* check OSC so we can get /progress messages. */
        osc_server->check();

        sleep(1);
    }

    WARNING( "Killed clients are still alive" );

    return;

done:

    MESSAGE( "All clients have died." );
}


void
command_all_clients_to_save ( )
{
    if ( session_path )
    {
        GUIMSG( "Commanding attached clients to save." );

        int save_error = save_session_file();

        if ( save_error == 1 ) {
            GUIMSG( "...but the session file is write protected. Will not forward save command to clients." );
            WARNING( "Aborting client save commands because the session file is write protected" );
            return;
        }



        for ( std::list<Client*>::iterator i = client.begin();
              i != client.end();
              ++i )
        {
            command_client_to_save( *i );
        }

        wait_for_replies();
    }
}

void
command_client_to_stop ( Client *c )
{
    GUIMSG( "Stopping client %s", c->name_with_id );

    if ( c->pid > 0 )
    {
        c->pending_command( COMMAND_KILL );

        kill( c->pid, SIGTERM );

        c->status = "stopped";
        if ( gui_is_active )
            osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );
    }
}

void
command_client_to_quit ( Client *c )
{
    MESSAGE( "Commanding %s to quit", c->name_with_id );

    if ( c->active )
    {
        c->pending_command( COMMAND_QUIT );

        kill( c->pid, SIGTERM );

        c->status = "quit";
        if ( gui_is_active )
            osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );
    }
    else if ( c->is_dumb_client() )
    {
        if ( c->pid > 0 )
        {
            c->status = "quit";
            if ( gui_is_active )
                osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );

            /* should be kill? */
            c->pending_command( COMMAND_QUIT );

            // this is a dumb client... try and kill it
            kill( c->pid, SIGTERM );
        }
        else
        {
            c->status = "removed";
            if ( gui_is_active )
                osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );
        }
    }
}

char *
get_lock_file_name( const char * session_name, const char * full_absolute_session_path )
{
    // To avoid collisions of two simple session names under either different subdirs o even different session roots.
    char *session_hash = simple_hash( full_absolute_session_path );

    char *session_lock;
    asprintf( &session_lock, "%s/%s%s", lockfile_directory, session_name, session_hash ); //lockfile_directory and session_name are variables in the current context.

    free(session_hash);
    return session_lock;
}


void
write_lock_file(  const char *filename, const char * session_path )
{
    //Not a GNU lockfile, which features were never used by nsmd anyway,
    //but simply a file with information about the NSM Server and the loaded session
    FILE *fp = fopen( filename, "w" );
    if ( !fp )  {
        FATAL( "Failed to write lock file to %s with error: %s", filename, strerror( errno ) );
    }

    fprintf( fp, "%s\n%s\n%d\n", session_path, osc_server->url(), getpid());
    MESSAGE( "Created lock file %s", filename );

    fclose( fp );
}

void
delete_lock_file( const char *filename )
{
    unlink( filename );
    MESSAGE( "Deleted lock file %s", filename );
}


void
close_session ( )
{
    if ( ! session_path )
        return;

    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        command_client_to_quit( *i );
    }

    wait_for_killed_clients_to_die();

    purge_inactive_clients();

    clear_clients();

    if ( session_path )
    {
        char * session_lock = get_lock_file_name( session_name, session_path);
        delete_lock_file( session_lock );
        MESSAGE( "Session %s was closed.", session_path  );

        free(session_lock);
        free(session_path);
        session_path = NULL;
        free(session_name);
        session_name = NULL;
    }

    if ( gui_is_active )
    {
        osc_server->send( gui_addr, "/nsm/gui/session/name", "", "" ); //Empty string = no current session
    }
}

void
tell_client_session_is_loaded( Client *c )
{
    if ( c->active )
//!c->is_dumb_client() )
    {
        MESSAGE( "Telling client %s that session is loaded.", c->name_with_id );
        osc_server->send( c->addr, "/nsm/client/session_is_loaded" );
    }
}

void
tell_all_clients_session_is_loaded ( void )
{
    MESSAGE( "Telling all clients that session is loaded..." );

   for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        tell_client_session_is_loaded( *i );
    }
}

int
load_session_file ( const char * path )
{
    //parameter "path" is the absolute path to the session including session root, without session.nsm

    //First check if the session file actually exists, before closing the current one
    const char * relative_session_path =  strdup( path ) + strlen( session_root ) + 1; //+1 for trailing /
    if ( session_already_exists( relative_session_path ) != 0) {
        WARNING ( "Instructed to load %s which does not exist. Doing nothing.", path );
        return ERR_NO_SUCH_FILE;
    }


    if ( session_path && session_name ) {
        //We are already in a session. This is switch, or load during duplicate etc.
        MESSAGE ( "Instructed to load %s while %s is still open. This is a normal operation. Attempting to switch clients intelligently, if they support it. Otherwise closing and re-opening.", path, session_path );
        char * session_lock = get_lock_file_name( session_name, session_path);
        delete_lock_file( session_lock );
    }

    set_name( path ); //Do this first so we have the simple name name for lockfiles and log messages

    char *session_file = NULL;
    asprintf( &session_file, "%s/session.nsm", path );

    //Check if the lockfile already exists, which means another nsmd currently has loaded the session we want to load.
    char * session_lock = get_lock_file_name( session_name, path );
    struct stat st_lockfile_exists_check;
    if ( stat (session_lock, &st_lockfile_exists_check) == 0)
    {
        WARNING( "Session %s is already loaded from another nsmd and locked by file %s", session_name, session_lock );

        free( session_file );
        free( session_lock );

        return ERR_SESSION_LOCKED;
    }


    FILE *fp;

    if ( ! ( fp = fopen( session_file, "r" ) ) )
    {
        free( session_file );
        return ERR_CREATE_FAILED;
    }

    free( session_file );

    session_path = strdup( path );


    std::list<Client*> new_clients;

    {
        char * client_name = NULL;
        char * client_executable = NULL;
        char * client_id = NULL;

        // load the client list
        while ( fscanf( fp, "%m[^:]:%m[^:]:%m[^:\n]\n", &client_name, &client_executable, &client_id ) > 0 )
        {
            Client *c = new Client();

            c->name = client_name;
            c->executable_path = client_executable;
            c->client_id =  client_id;

            asprintf( &c->name_with_id, "%s.%s", c->name, c->client_id );

            new_clients.push_back( c );
        }
    }

    fclose(fp);

    MESSAGE( "Commanding unneeded and dumb clients to quit" );

    std::map<std::string,int> client_map;

    /* count how many instances of each client are needed in the new session */
    for ( std::list<Client*>::iterator i = new_clients.begin();
          i != new_clients.end();
          ++i )
    {
        if ( client_map.find( (*i)->name) != client_map.end() )
            client_map[(*i)->name]++;
        else
            client_map[(*i)->name] = 1;
    }

    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        if ( ! (*i)->is_capable_of( ":switch:" ) || client_map.find((*i)->name ) == client_map.end()  )
        {
            /* client is not capable of switch, or is not wanted in the new session */
            command_client_to_quit( *i );
        }
        else
        {
            /* client is switch capable and may be wanted in the new session */
            if ( client_map[ (*i)->name ]-- <= 0 )
                /* nope,, we already have as many as we need, stop this one */
                command_client_to_quit( *i );
        }

    }

//    wait_for_replies();

    wait_for_killed_clients_to_die();

//    wait_for_dumb_clients_to_die();

    purge_inactive_clients();

    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        (*i)->pre_existing = true;
    }

    MESSAGE( "Commanding smart clients to switch" );

    for ( std::list<Client*>::iterator i = new_clients.begin();
          i != new_clients.end();
          ++i )
    {
        Client *c = NULL;

        /* in a duplicated session, clients will have the same
         * IDs, so be sure to pick the right one to avoid race
         * conditions in JACK name registration. */
        c = get_client_by_name_and_id( &client, (*i)->name, (*i)->client_id );

        if ( ! c )
            c = client_by_name( (*i)->name, &client );

        if ( c && c->pre_existing && !c->reply_pending() )
        {
            // since we already shutdown clients not capable of 'switch', we can assume that these are.
            command_client_to_switch( c, (*i)->client_id );
        }
        else
        {
            /* sleep a little bit because liblo derives its sequence
             * of port numbers from the system time (second
             * resolution) and if too many clients start at once they
             * won't be able to find a free port. */
            usleep( 100 * 1000 );

            launch( (*i)->executable_path, (*i)->client_id );
        }
    }

    /* this part is a little tricky... the clients need some time to
     * send their 'announce' messages before we can send them 'open'
     * and know that a reply is pending and we should continue waiting
     * until they finish. wait_for_replies() must check for OSC
     * messages immediately, even if no replies seem to be pending
     * yet. */

    /* dumb clients will never send an 'announce message', so we need
     * to give up waiting on them fairly soon. */

    wait_for_announce();

    wait_for_replies();

    tell_all_clients_session_is_loaded();

    //We already checked if the logfile exists above, and it didn't.
    //We also tested for write permissions to our XDG run-dir, which we confirmed to have.
    //We can create the lockfile now.
    write_lock_file( session_lock, session_path );
    MESSAGE( "Session %s was loaded.", session_path);

    new_clients.clear();

    if ( gui_is_active )
    {   //This is not the case when --load-session was used. GUI announce will come later.

        //Send two parameters to signal that the session was loaded: simple session-name, relative session path below session root
        MESSAGE( "Informing GUI about running session name: %s with relative path %s", session_name, session_path + strlen( session_root ) );
        osc_server->send( gui_addr, "/nsm/gui/session/name", session_name, session_path + strlen( session_root ));
    }

    return ERR_OK;
}

OSC_HANDLER( save )
{
    lo_address sender_addr;
    sender_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ));

    if ( pending_operation != COMMAND_NONE )
    {
            osc_server->send( sender_addr, "/error", path,
                              ERR_OPERATION_PENDING,
                              "An operation pending." );
            return 0;
    }

    if ( ! session_path )
    {
        osc_server->send( sender_addr, "/error", path,
                          ERR_NO_SESSION_OPEN,
                          "No session to save.");

        goto done;
    }

    command_all_clients_to_save();

    MESSAGE( "Done." );

    osc_server->send( sender_addr, "/reply", path, "Saved." );

done:

    pending_operation = COMMAND_NONE;

    return 0;
}

OSC_HANDLER( duplicate )
{
    lo_address sender_addr;
    sender_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ));

    if ( pending_operation != COMMAND_NONE )
    {
            osc_server->send( sender_addr, "/error", path,
                              ERR_OPERATION_PENDING,
                              "An operation pending." );
            return 0;
    }

    pending_operation = COMMAND_DUPLICATE;

    if ( ! session_path )
    {
        osc_server->send( sender_addr, "/error", path,
                          ERR_NO_SESSION_OPEN,
                          "No session to duplicate.");
        goto done;
    }

    if ( ! path_is_valid( &argv[0]->s ) )
    {
        osc_server->send( sender_addr, "/error", path,
                          ERR_CREATE_FAILED,
                          "Invalid session name." );

        goto done;
    }

    if ( session_already_exists(&argv[0]->s) == 0) {
        osc_server->send( sender_addr, "/error", path,
                          ERR_CREATE_FAILED,
                          "Session name already exists." );

        pending_operation = COMMAND_NONE;

        return 0;
    }

    command_all_clients_to_save();

    if ( clients_have_errors() )
    {
        osc_server->send( sender_addr, "/error", path,
                          ERR_GENERAL_ERROR,
                          "Some clients could not save" );

        goto done;
    }

//    save_session_file();

    char *spath;
    asprintf( &spath, "%s/%s", session_root, &argv[0]->s );

    mkpath( spath, false );

    /* FIXME: code a recursive copy instead of calling the shell */

    char *cmd;
    asprintf( &cmd, "cp -R \"%s\" \"%s\"", session_path, spath);

    system( cmd );

    free( cmd );

    osc_server->send( gui_addr,  "/nsm/gui/session/session", &argv[0]->s  );

    MESSAGE( "Attempting to open during DUPLICATE: %s", spath );

    //The original session is still open. load_session_file will close it, and possibly ::switch::

    if ( !load_session_file( spath ) )
    {
        MESSAGE( "Loaded" );
        osc_server->send( sender_addr, "/reply", path,
                          "Loaded." );
    }
    else
    {
        MESSAGE( "Failed" );
        osc_server->send( sender_addr, "/error", path,
                          ERR_NO_SUCH_FILE,
                          "No such file." );
        free(spath);
        pending_operation = COMMAND_NONE;
        return -1;
    }

    free( spath );

    MESSAGE( "Done" );

    osc_server->send( sender_addr, "/reply", path, "Duplicated." );

done:

    pending_operation = COMMAND_NONE;

    return 0;
}

OSC_HANDLER( new )
{
    lo_address sender_addr;
    sender_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ));

    if ( pending_operation != COMMAND_NONE )
    {
            osc_server->send( sender_addr, "/error", path,
                              ERR_OPERATION_PENDING,
                              "An operation pending." );
            return 0;
    }

    pending_operation = COMMAND_NEW;

    if ( ! path_is_valid( &argv[0]->s ) )
    {
        osc_server->send( sender_addr, "/error", path,
                          ERR_CREATE_FAILED,
                          "Invalid session name." );

        pending_operation = COMMAND_NONE;

        return 0;
    }

    if ( session_already_exists(&argv[0]->s) == 0) {
        osc_server->send( sender_addr, "/error", path,
                          ERR_CREATE_FAILED,
                          "Session name already exists." );

        pending_operation = COMMAND_NONE;

        return 0;
    }


    if ( session_path ) //Already a session running?
    {
        command_all_clients_to_save();

        close_session();
    }

    GUIMSG( "Creating new session \"%s\"", &argv[0]->s );

    char *spath;
    asprintf( &spath, "%s/%s", session_root, &argv[0]->s );

    if ( mkpath( spath, true ) )
    {
        osc_server->send( sender_addr, "/error", path,
                          ERR_CREATE_FAILED,
                          "Could not create the session directory" );

        free(spath);

        pending_operation = COMMAND_NONE;

        return 0;
    }

    session_path = strdup( spath );

    set_name( session_path );

    char * session_lock = get_lock_file_name( session_name, session_path);
    write_lock_file( session_lock, session_path );
    free ( session_lock );


    osc_server->send( sender_addr, "/reply", path, "Created." );

    if ( gui_is_active )
    {
        osc_server->send( gui_addr,  "/nsm/gui/session/session", &argv[0]->s  );

        //Send two parameters to signal that the session was loaded: simple session-name, relative session path below session root
        MESSAGE( "Informing GUI about running session name: %s with relative path %s", session_name, session_path + strlen( session_root ) );
        osc_server->send( gui_addr, "/nsm/gui/session/name", session_name, session_path + strlen( session_root ));
    }

    save_session_file();

    free( spath );

    osc_server->send( sender_addr, "/reply", path, "Session created" );

    pending_operation = COMMAND_NONE;

    return 0;
}

int
fts_comparer_to_process_files_before_dirs( const FTSENT ** first, const FTSENT ** second )
{
    /*
       The argument compar() specifies a user-defined function which may
       be used to order the traversal of the hierarchy.  It takes two
       pointers to pointers to FTSENT structures as arguments and should
       return a negative value, zero, or a positive value to indicate if
       the file referenced by its first argument comes before, in any
       order with respect to, or after, the file referenced by its
       second argument.  The fts_accpath, fts_path, and fts_pathlen
       fields of the FTSENT structures may never be used in this
       comparison.  If the fts_info field is set to FTS_NS or FTS_NSOK,
       the fts_statp field may not either.  If the compar() argument is
       NULL, the directory traversal order is in the order listed in
       path_argv for the root paths, and in the order listed in the
       directory for everything else.
    */

    if ( (*first)->fts_info & FTS_F )
        return (-1); //first
    else if ( (*second)->fts_info & FTS_F )
        return (1); //last
    else
        return strcmp((*first)->fts_name, (*second)->fts_name);
        //return (0); //doesn't matter
}


static lo_address list_response_address;

OSC_HANDLER( list )
{
    //Parse the session_root recursively for session.nsm files and send names with /nsm/server/list
    //Sessions can be structured with sub-directories.
    //The file session.nsm marks a real session and is a 'leaf' of the session tree.
    //No other sessions are allowed below a dir containing session.nsm .

    GUIMSG( "Listing sessions" );
    list_response_address = lo_message_get_source( msg );

    //Use fts to walk the session_root
    /* An array of paths to traverse. Each path must be null
     * terminated and the list must end with a NULL pointer. */
    char *paths[] = { session_root, NULL };

    /* 2nd parameter: An options parameter. Must include either
       FTS_PHYSICAL or FTS_LOGICAL---they change how symbolic links
       are handled.

       Last parameter is a comparator which you can optionally provide
       to change the traversal of the filesystem hierarchy.

       Our comparator processes files before directories, so we can depend on that
       to remember if we are already in a session-dir.
    */

    FTS *ftsp = fts_open(paths, FTS_LOGICAL, fts_comparer_to_process_files_before_dirs);
    if(ftsp == NULL)
    {
        FATAL( "fts_open" );
        exit(EXIT_FAILURE);
    }

    FTSENT * currentSession = NULL;
    while( 1 ) // call fts_read() enough times to get each file
    {
        FTSENT *ent = fts_read(ftsp); // get next entry (could be file or directory).
        if( ent == NULL )
        {
            if( errno == 0 )
                break; // No more items, bail out of while loop
            else
            {
                // fts_read() had an error.
                FATAL( "fts_read" );
                exit(EXIT_FAILURE);
            }
        }

        // Handle Types of Files

        // Given a "entry", determine if it is a file or directory
        if( ent->fts_info & FTS_D )   // We are entering into a directory
            {
                //printf( "Enter dir: %s\n", ent->fts_path );
                if ( currentSession != NULL )
                {
                    //printf( "already found current session.nsm: %s  . Waiting to leave dir. Ignoring %s\n", currentSession->fts_name, ent->fts_path );

                    // Setup that no descendants of this file are visited.
                    int err = fts_set( ftsp, ent, FTS_SKIP );
                    if ( err != 0 )
                    {
                        FATAL( "fts_set" );
                        exit(EXIT_FAILURE);
                    }
                }
            }
        else if( ent->fts_info & FTS_DP ) // We are exiting a directory
            {
                //printf( "Exit dir: %s\n", ent->fts_path );
                if ( ent == currentSession )
                {
                    //printf( "Exit current session dir: %s\n", ent->fts_path );
                    currentSession = NULL;
                }
            }
        else if( ent->fts_info & FTS_F ) // The entry is a file.
        {
            //printf( "File: %s\n", ent->fts_path );
            if ( ! strcmp( "session.nsm", basename( ent->fts_path ) ) )
            {
                //Convert path to session name:
                char *s;
                s = strdup( ent->fts_path );
                s = dirname( s );
                memmove( s, s + strlen( session_root ) + 1, (strlen( s ) - strlen( session_root )) + 1);
                osc_server->send( list_response_address, "/reply", "/nsm/server/list", s  );
                free( s );
                currentSession = ent->fts_parent; //save the directory entry. not the session.nsm entry.
            }
        }
    }

    // close fts and check for error closing.
    if(fts_close(ftsp) == -1)
        FATAL( "fts_close" );

    // As marker that all sessions were sent reply with an empty string, which is impossible to conflict with a session name
    osc_server->send( list_response_address, "/reply", "/nsm/server/list", "" );
    return 0;
}


OSC_HANDLER( open )
{
    lo_address sender_addr;
    sender_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ));

    GUIMSG( "Opening session %s", &argv[0]->s );

    if ( pending_operation != COMMAND_NONE )
    {
            osc_server->send( sender_addr, "/error", path,
                              ERR_OPERATION_PENDING,
                              "An operation pending." );
            return 0;
    }

    pending_operation = COMMAND_OPEN;

    if ( session_path )
    {

        command_all_clients_to_save();

        if ( clients_have_errors() )
        {
            osc_server->send( sender_addr, "/error", path,
                              ERR_GENERAL_ERROR,
                              "Some clients could not save" );

            pending_operation = COMMAND_NONE;
            return 0;
        }

//        save_session_file();
    }


    char *spath;
    asprintf( &spath, "%s/%s", session_root, &argv[0]->s );

    MESSAGE( "Attempting to open %s", spath );

    int err = load_session_file( spath );

    if ( ! err )
    {
        MESSAGE( "Loaded" );
        osc_server->send( sender_addr, "/reply", path,
                          "Loaded." );
    }
    else
    {
        MESSAGE( "Failed" );
        const char *m = NULL;
        switch ( err )
        {
            case ERR_CREATE_FAILED:
                m = "Could not create session file!";
                break;
            case ERR_SESSION_LOCKED:
                m = "Session is locked by another process!";
                break;
            case ERR_NO_SUCH_FILE:
                m = "The named session does not exist.";
                break;
            default:
                m = "Unknown error";
        }


        osc_server->send( sender_addr, "/error", path,
                          err,
                          m );
    }

    free( spath );

    MESSAGE( "Done" );

    pending_operation = COMMAND_NONE;

    return 0;
}


OSC_HANDLER( quit )
{
    close_session();

    handle_signal_clean_exit(0);

    return 0;
}

OSC_HANDLER( abort )
{
    lo_address sender_addr;
    sender_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ));

    if ( pending_operation != COMMAND_NONE )
        {
                osc_server->send( sender_addr, "/error", path,
                                ERR_OPERATION_PENDING,
                                "An operation pending." );
                return 0;
        }

    pending_operation = COMMAND_CLOSE;


    if ( ! session_path )
    {
        osc_server->send( sender_addr, "/error", path,
                          ERR_NO_SESSION_OPEN,
                          "No session to abort." );

        goto done;
    }

    GUIMSG( "Commanding attached clients to quit." );

    close_session();

    osc_server->send( sender_addr, "/reply", path,
                      "Aborted." );

    MESSAGE( "Done" );

done:

    pending_operation = COMMAND_NONE;

    return 0;
}

OSC_HANDLER( close )
{
    lo_address sender_addr;
    sender_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) ));

    if ( pending_operation != COMMAND_NONE )
    {
            osc_server->send( sender_addr, "/error", path,
                              ERR_OPERATION_PENDING,
                              "An operation pending." );
            return 0;
    }

    pending_operation = COMMAND_CLOSE;

    if ( ! session_path )
    {
        osc_server->send( sender_addr, "/error", path,
                          ERR_NO_SESSION_OPEN,
                          "No session to close." );

        goto done;
    }
    command_all_clients_to_save();

    GUIMSG( "Commanding attached clients to quit." );

    close_session();

    osc_server->send( sender_addr, "/reply", path, "Closed." );

    MESSAGE( "Done" );

done:

    pending_operation = COMMAND_NONE;

    return 0;
}


OSC_HANDLER( broadcast )
{
    const char *to_path = &argv[0]->s;

    /* don't allow clients to broadcast NSM commands */
    if ( ! strncmp( to_path, "/nsm/", strlen( "/nsm/" ) ) )
        return 0;

    std::list<OSC::OSC_Value> new_args;

    for ( int i = 1; i < argc; ++i )
    {
        switch ( types[i] )
        {
            case 's':
                new_args.push_back( OSC::OSC_String( &argv[i]->s ) );
                break;
            case 'i':
                new_args.push_back( OSC::OSC_Int( argv[i]->i ) );
                break;
            case 'f':
                new_args.push_back( OSC::OSC_Float( argv[i]->f ) );
                break;
        }
    }

    char *sender_url = lo_address_get_url( lo_message_get_source( msg ) );

    for ( std::list<Client*>::iterator i = client.begin();
          i != client.end();
          ++i )
    {
        if ( ! (*i)->addr )
            continue;

        char *url = lo_address_get_url( (*i)->addr );

        if ( strcmp( sender_url, url ) )
        {
            osc_server->send( (*i)->addr, to_path, new_args );
        }

        free( url );
    }

    /* also relay to attached GUI so that the broadcast can be
     * propagated to another NSMD instance */
    if ( gui_is_active )
    {

        char *u1 = lo_address_get_url( gui_addr );

        if ( strcmp( u1, sender_url ) )
        {
            new_args.push_front( OSC::OSC_String( to_path ) );

            osc_server->send( gui_addr, path, new_args );
        }

        free(u1);
    }

    free( sender_url );

    return 0;
}



/*********************************/
/* Client Informational Messages */
/*********************************/

OSC_HANDLER( progress )
{
    Client *c = get_client_by_address( lo_message_get_source( msg ) );

    if ( c )
    {
        c->progress = argv[0]->f;

        /* MESSAGE( "%s progress: %i%%", c->name, (int)(c->progress * 100.0f) ); */

        if ( gui_is_active )
        {
            osc_server->send( gui_addr, "/nsm/gui/client/progress", c->client_id, (float)c->progress );
        }
    }

    return 0;
}

OSC_HANDLER( is_dirty )
{
    MESSAGE( "Client sends dirty" );

    Client *c = get_client_by_address( lo_message_get_source( msg ) );

    if ( ! c )
        return 0;

    c->dirty = 1;

    if ( gui_is_active )
        osc_server->send( gui_addr, "/nsm/gui/client/dirty", c->client_id, c->dirty );

    return 0;
}

OSC_HANDLER( is_clean )
{
    MESSAGE( "Client sends clean" );

    Client *c = get_client_by_address( lo_message_get_source( msg ) );

    if ( ! c )
        return 0;

    c->dirty = 0;

    if ( gui_is_active )
        osc_server->send( gui_addr, "/nsm/gui/client/dirty", c->client_id, c->dirty );

    return 0;
}

OSC_HANDLER( gui_is_hidden )
{
    MESSAGE( "Client sends gui hidden" );

    Client *c = get_client_by_address( lo_message_get_source( msg ) );

    if ( ! c )
        return 0;

    c->gui_visible( false );

    if ( gui_is_active )
        osc_server->send( gui_addr, "/nsm/gui/client/gui_visible", c->client_id, c->gui_visible() );

    return 0;
}

OSC_HANDLER( gui_is_shown )
{
    MESSAGE( "Client sends gui shown" );

    Client *c = get_client_by_address( lo_message_get_source( msg ) );

    if ( ! c )
        return 0;

    c->gui_visible( true );

    if ( gui_is_active )
        osc_server->send( gui_addr, "/nsm/gui/client/gui_visible", c->client_id, c->gui_visible() );

    return 0;
}

OSC_HANDLER( message )
{
    Client *c = get_client_by_address( lo_message_get_source( msg ) );

    if ( ! c )
        return 0;

    if ( gui_is_active )
        osc_server->send( gui_addr, "/nsm/gui/client/message", c->client_id, argv[0]->i, &argv[1]->s );

    return 0;
}

OSC_HANDLER( label )
{
    Client *c = get_client_by_address( lo_message_get_source( msg ) );

    if ( ! c )
        return 0;

    if ( strcmp( types, "s" ) )
        return -1;

    c->label( &argv[0]->s );
    if ( gui_is_active )
        osc_server->send( gui_addr, "/nsm/gui/client/label", c->client_id, c->label() );

    return 0;
}

/**********************/
/* Response Handlers  */
/**********************/


OSC_HANDLER( error )
{
    Client *c = get_client_by_address( lo_message_get_source( msg ) );

    if ( ! c )
    {
        WARNING( "Error from unknown client" );
        return 0;
    }

//    const char *rpath = &argv[0]->s;

    int err_code = argv[1]->i;

    const char *message = &argv[2]->s;

    c->set_reply( err_code, message );

    MESSAGE( "Client \"%s\" replied with error: %s (%i) in %fms", c->name_with_id, message, err_code, c->milliseconds_since_last_command() );
    c->pending_command( COMMAND_NONE );

    c->status = "error";
    if ( gui_is_active )
        osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );

    return 0;
}


OSC_HANDLER( reply )
{
    Client *c = get_client_by_address( lo_message_get_source( msg ) );

//    const char *rpath = &argv[0]->s;
    const char *message = &argv[1]->s;

    if ( c )
    {
        c->set_reply( ERR_OK, message );

        MESSAGE( "Client \"%s\" replied with: %s in %fms", c->name_with_id, message, c->milliseconds_since_last_command() );

        c->pending_command( COMMAND_NONE );

        c->status = "ready";
        if ( gui_is_active )
            osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );
    }
    else
        MESSAGE( "Reply from unknown client" );

    return 0;
}



/******************/
/* GUI operations */
/******************/


OSC_HANDLER( stop )
{
    Client *c = get_client_by_id( &client, &argv[0]->s );

    if ( c )
    {
        command_client_to_stop( c );

        if ( gui_is_active )
            osc_server->send( gui_addr, "/reply", "Client stopped." );
    }
    else
    {
        if ( gui_is_active )
            osc_server->send( gui_addr, "/error", -10, "No such client." );
    }


    return 0;
}

OSC_HANDLER( remove )
{
    Client *c = get_client_by_id( &client, &argv[0]->s );

    if ( c )
    {
        if ( c->pid == 0 &&
             ! c->active )
        {

            c->status = "removed";
            if ( gui_is_active )
                osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );

            client.remove( c );

            delete c;

            if ( gui_is_active )
                osc_server->send( gui_addr, "/reply", "Client removed." );
        }
    }
    else
    {
        if ( gui_is_active )
            osc_server->send( gui_addr, "/error", -10, "No such client." );
    }


    return 0;
}

OSC_HANDLER( resume )
{
    Client *c = get_client_by_id( &client, &argv[0]->s );

    /* FIXME: return error if no such client? */
    if ( c )
    {
        if ( c->pid == 0 &&
             ! c->active )
        {
            if ( ! launch( c->executable_path, c->client_id ) )
            {

            }
        }
    }

    return 0;
}


OSC_HANDLER( client_save )
{
    Client *c = get_client_by_id( &client, &argv[0]->s );

    /* FIXME: return error if no such client? */
    if ( c )
    {
        if ( c->active )
        {
            command_client_to_save( c );
        }
    }

    return 0;
}

OSC_HANDLER( client_show_optional_gui )
{
    Client *c = get_client_by_id( &client, &argv[0]->s );

    /* FIXME: return error if no such client? */
    if ( c )
    {
        if ( c->active )
        {
            osc_server->send( c->addr, "/nsm/client/show_optional_gui" );
        }
    }

    return 0;
}

OSC_HANDLER( client_hide_optional_gui )
{
    Client *c = get_client_by_id( &client, &argv[0]->s );

    /* FIXME: return error if no such client? */
    if ( c )
    {
        if ( c->active )
        {
            osc_server->send( c->addr, "/nsm/client/hide_optional_gui" );
        }
    }

    return 0;
}

void
announce_gui( const char *url, bool is_reply )
{
    // This is send for a new and empty nsmd as well as already running, headless, ones.
    // If a GUI connects to an existing server with a running session this will trigger a list of
    // clients send to the new GUI.

    MESSAGE ( "A GUI announced to us from the URL %s", url );

    gui_addr = lo_address_new_from_url( url );
    gui_is_active = true; //global state

    if ( is_reply )
        // the default case. A GUI starts its own nsmd or connects to a running one
        osc_server->send( gui_addr, "/nsm/gui/gui_announce", "hi" );
    else
        //The server was started directly and instructed to connect to a running GUI.
        osc_server->send( gui_addr, "/nsm/gui/server_announce", "hi" );

    //The session root is not inluced in /nsm/gui/session/name
    //For the general information we need to send this message:
    osc_server->send( gui_addr, "/nsm/gui/session/root", session_root );



    //Send session name and relative path. If both are empty it signals that no session is currently open,
    //which is the default state if a GUI started nsmd.
    //No session_path without session_name. We only need to test for session_name.
    if ( !session_name || session_name[0]  == '\0' )
    {
        MESSAGE( "Informing GUI that no session is running by sending two empty strings" );
        osc_server->send( gui_addr, "/nsm/gui/session/name", "", "" ); //Empty string = no current session
    }
    else
    {
        // Send a list of clients to the newly registered GUI in case there was already a session open
        // First clients, then session name was original nsmd order.
        // We keep it that way, the only change is that we made even the attempt dependent on a running session.
        MESSAGE ( "Informing GUI about %li already running clients", client.size() );
        for ( std::list<Client*>::iterator i = client.begin();
              i != client.end();
              ++i )
        {
            Client *c = *i;
            osc_server->send( gui_addr, "/nsm/gui/client/new", c->client_id, c->executable_path ); // we send new twice. see announce() comment
            if ( c->status )
                osc_server->send( gui_addr, "/nsm/gui/client/status", c->client_id, c->status );
            if ( c->is_capable_of( ":optional-gui:" ) )
                osc_server->send( gui_addr, "/nsm/gui/client/has_optional_gui", c->client_id );
            if ( c->label() ) // could be NULL
                osc_server->send( gui_addr, "/nsm/gui/client/label", c->client_id, c->label() );
            if ( c->active )
                osc_server->send( gui_addr, "/nsm/gui/client/new", c->client_id, c->name ); // upgrade to pretty-name
        }

        MESSAGE( "Informing GUI about running session name: %s with relative path %s", session_name, session_path + strlen( session_root ) );
        osc_server->send( gui_addr, "/nsm/gui/session/name", session_name, session_path + strlen( session_root ));
    }

    MESSAGE( "Registration with GUI complete" );
}


OSC_HANDLER( gui_announce )
{
    announce_gui( lo_address_get_url( lo_message_get_source( msg ) ), true );

    return 0;
}

OSC_HANDLER( ping )
{
    osc_server->send( lo_message_get_source( msg ), "/reply", path );

    return 0;
}

OSC_HANDLER( null )
{
    WARNING( "Unrecognized message with type signature \"%s\" at path \"%s\"", types, path );

    return 0;
}



static void
wait ( long timeout )
{
    struct signalfd_siginfo fdsi;

    ssize_t s = read(signal_fd, &fdsi, sizeof(struct signalfd_siginfo));

    if (s == sizeof(struct signalfd_siginfo))
    {
        if (fdsi.ssi_signo == SIGCHLD)
                handle_sigchld();
    }

    osc_server->wait( timeout );

    purge_dead_clients();
}


void
handle_signal_clean_exit ( int signal )
{
    WARNING( "Caught SIGNAL %i. Stopping nsmd.", signal);
    // We want a clean exit even when things go wrong.
    close_session();
    free( session_root );
    free( lockfile_directory );
    unlink( daemon_file );
    MESSAGE( "Deleted daemon file %s", daemon_file );
    free( daemon_file );

    exit(0);
}


int main(int argc, char *argv[])
{
     signal(SIGINT, handle_signal_clean_exit);
     signal(SIGTERM, handle_signal_clean_exit);
     signal(SIGSEGV, handle_signal_clean_exit);


    sigset_t mask;
    sigemptyset( &mask );
    sigaddset( &mask, SIGCHLD );
    sigprocmask(SIG_BLOCK, &mask, NULL );
    signal_fd = signalfd( -1, &mask, SFD_NONBLOCK );

    /* generate random seed for client ids */
    {
        time_t seconds;
        time(&seconds);

        srand( (unsigned int) seconds );
    }

    //Command line parameters
    char *osc_port = NULL;
    const char *gui_url = NULL;
    const char *load_session = NULL;

    static struct option long_options[] =
    {
        { "detach", no_argument, 0, 'd' },
        { "session-root", required_argument, 0, 's' },
        { "osc-port", required_argument, 0, 'p' },
        { "gui-url", required_argument, 0, 'g' },
        { "help", no_argument, 0, 'h' },
        { "version", no_argument, 0, 'v' },
        { "load-session", required_argument, 0, 'l'},
        { "quiet", no_argument, 0, 'q'},  //supresses all normal MESSAGE except WARNING and FATAL
        { 0, 0, 0, 0 }
    };

    int option_index = 0;
    int c = 0;

    bool detach = false;

    while ( ( c = getopt_long_only( argc, argv, "", long_options, &option_index  ) ) != -1 )
    {
        switch ( c )
        {
            case 'd':
                detach = true;
                break;
            case 's':
            {
                session_root = strdup(optarg);
                /* get rid of trailing slash */
                char *s = rindex(session_root,'/');
                if ( s == &session_root[strlen(session_root) - 1] )
                    *s = '\0';
                break;
            }
            case 'p':
                MESSAGE( "Using OSC port %s", optarg );
                osc_port = optarg;
                break;
            case 'g':
                MESSAGE( "Going to connect to GUI at: %s", optarg );
                gui_url = optarg;
                break;
            case 'l':
                MESSAGE( "Loading existing session file %s", optarg);
                load_session = optarg;
                break;
            case 'v':
                printf( "%s " VERSION_STRING "\n", argv[0] );
                exit(0);
                break;
            case 'q':
                quietMessages = true; //from debug.h
                break;
            case 'h':
                //Print usage message according to POSIX.1-2017
                const char *usage =
                "nsmd - Daemon and server for the 'New Session Manager'\n\n"
                "Usage:\n"
                "  nsmd\n"
                "  nsmd --help\n"
                "  nsmd --version\n"
                "\n"
                "Options:\n"
                "  --help                Show this screen\n"
                "  --version             Show version\n"
                "  --osc-port portnum    OSC port number [Default: provided by system].\n"
                "  --session-root path   Base path for sessions [Default: $XDG_DATA_HOME/nsm/].\n"
                "  --load-session name   Load existing session [Example: \"My Song\"].\n"
                "  --gui-url url         Connect to running legacy-gui [Example: osc.udp://mycomputer.localdomain:38356/].\n"
                "  --detach              Detach from console.\n"
                "  --quiet               Suppress messages except warnings and errors.\n"
                "\n\n"
                "nsmd can be run headless with existing sessions. To create new ones it is recommended to use a GUI\n"
                "such as nsm-legacy-gui (included) or Agordejo (separate package)\n"
                "";
                puts ( usage );
                exit(0);
                break;
        }
    }


    //Get the XDG runtime directory for lockfiles
    //https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
    //Unlike $XDG_DATA_HOME the runtime env var must be set, usually to /run/user/1000/
    struct stat rundir_check;
    lockfile_directory = getenv( "XDG_RUNTIME_DIR" );
    if ( stat( lockfile_directory, &rundir_check ) != 0 && S_ISDIR(rundir_check.st_mode)) {
        FATAL( "Failed to access $XDG_RUNTIME_DIR directory %s with error: %s", lockfile_directory, strerror( errno ) );
    }
    else {
        asprintf( &lockfile_directory, "%s/%s", lockfile_directory, "nsm");

        //Create the 'nsm' subdirectory. This may fail on it's own.
        struct stat st_lockfile_dir_mkdir;
        if ( stat( lockfile_directory, &st_lockfile_dir_mkdir ) )
        {
            if ( mkdir( lockfile_directory, 0771 ) )
            {
                FATAL( "Failed to create lock file directory %s with error: %s", lockfile_directory, strerror( errno ) );
            }
        }
        MESSAGE( "Using %s for lock-files.", lockfile_directory );

        //Now create another subdir for daemons .../nsm/d/ where each daemon has a port number file
        char * daemon_directory;
        asprintf( &daemon_directory, "%s/d", lockfile_directory);
        struct stat st_daemonfile_dir_mkdir;
        if ( stat( daemon_directory, &st_daemonfile_dir_mkdir ) )
        {
            if ( mkdir( daemon_directory, 0771 ) )
            {
                FATAL( "Failed to create daemon file directory %s with error: %s", daemon_directory, strerror( errno ) );
            }
        }
        //daemon_file is a global var
        asprintf( &daemon_file, "%s/%d", daemon_directory, getpid());
        free ( daemon_directory );
        MESSAGE( "Using %s as daemon file.", daemon_file );
        //The actual daemon file will be written below after announcing the session url.
    }

    if ( !session_root ) {
        /* The user gave no specific session directory. We use the default.
        * The default dir follows the XDG Basedir Specifications:
        * It is used by looking up environment variables.
        * https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
        *    $XDG_DATA_HOME defines the base directory relative to which user-specific data files
        *    should be stored. If $XDG_DATA_HOME is either not set or empty, a default equal to
        *    $HOME/.local/share should be used.
        *
        * Up to version 1.5.3 the default dir was ~/NSM Sessions .
        * If this old directory exists we will use it but write mild warning to the log.
        * Moving old sessions is left to the user, or an external GUI.
        */
        struct stat st_session_root;

        //TODO: Valgrind shows a memory leak for the next line. Why?
        asprintf( &session_root, "%s/%s", getenv( "HOME" ), "NSM Sessions" );
        if ( stat( session_root, &st_session_root ) == 0 && S_ISDIR(st_session_root.st_mode)) {
            WARNING ( "An old session directory was detected in %s. You can continue to use it but it is recommended to move your sessions to $XDG_DATA_HOME/nsm-sessions/. If you don't know where that is simply rename your current session-directory and start nsmd, which will tell you the new directory.", session_root);
        }
        else {
            const char *xdg_data_home = getenv( "XDG_DATA_HOME" );
            if ( xdg_data_home ) {
                //If $XDG_DATA_HOME is explicitly set by the user we assume it to exist. We don't want to recursively create system directories.
                //If the xdg-dir does not exist yet we FATAL out just below.
                asprintf( &session_root, "%s/%s", xdg_data_home, "nsm" );
            }
            else {
                //If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
                asprintf( &session_root, "%s/.local/share/nsm", getenv( "HOME" ));
            }
        }
    }

    struct stat st_session_root_mkdir;

    if ( stat( session_root, &st_session_root_mkdir ) )
    {
        if ( mkdir( session_root, 0771 ) )
        {
            FATAL( "Failed to create session directory %s with error: %s", session_root, strerror( errno ) );
        }
    }



    MESSAGE( "Session root is: %s", session_root );

    osc_server = new OSC::Endpoint();

    if ( osc_server->init( LO_UDP, osc_port ) )
    {
        WARNING( "Failed to create OSC server. Exiting." );
        exit( 1 );
    }

    char * url = osc_server->url();

    printf( "NSM_URL=%s\n", url );

    //Write the URL into the daemon_file that is named after our PID
    FILE *fpdaemon = fopen( daemon_file, "w" );
    if ( !fpdaemon )  {
        FATAL( "Failed to write daemon file to %s with error: %s", daemon_file, strerror( errno ) );
    }
    fprintf( fpdaemon, "%s\n", url );
    MESSAGE( "Created daemon file %s", daemon_file );
    fclose( fpdaemon );

    free( url );

    if ( gui_url )
    {
        //The server was started directly and instructed to connect to a running GUI.
        announce_gui( gui_url, false );
    }

    /*  */
    osc_server->add_method( "/nsm/server/announce", "sssiii", OSC_NAME( announce ), NULL, "client_name,capabilities,executable,api_version_major,api_version_minor,client_pid" );

    /* response handlers */
    osc_server->add_method( "/reply", "ss", OSC_NAME( reply ), NULL, "err_code,msg" );
    osc_server->add_method( "/error", "sis", OSC_NAME( error ), NULL, "err_code,msg" );

    osc_server->add_method( "/nsm/client/progress", "f", OSC_NAME( progress ), NULL, "progress" );
    osc_server->add_method( "/nsm/client/is_dirty", "", OSC_NAME( is_dirty ), NULL, "dirtiness" );
    osc_server->add_method( "/nsm/client/is_clean", "", OSC_NAME( is_clean ), NULL, "dirtiness" );
    osc_server->add_method( "/nsm/client/message", "is", OSC_NAME( message ), NULL, "message" );
    osc_server->add_method( "/nsm/client/gui_is_hidden", "", OSC_NAME( gui_is_hidden ), NULL, "message" );
    osc_server->add_method( "/nsm/client/gui_is_shown", "", OSC_NAME( gui_is_shown ), NULL, "message" );
    osc_server->add_method( "/nsm/client/label", "s", OSC_NAME( label ), NULL, "message" );

    /*  */
    osc_server->add_method( "/nsm/gui/gui_announce", "", OSC_NAME( gui_announce ), NULL, "" );
    osc_server->add_method( "/nsm/gui/client/stop", "s", OSC_NAME( stop ), NULL, "client_id" );
    osc_server->add_method( "/nsm/gui/client/remove", "s", OSC_NAME( remove ), NULL, "client_id" );
    osc_server->add_method( "/nsm/gui/client/resume", "s", OSC_NAME( resume ), NULL, "client_id" );
    osc_server->add_method( "/nsm/gui/client/save", "s", OSC_NAME( client_save ), NULL, "client_id" );
    osc_server->add_method( "/nsm/gui/client/show_optional_gui", "s", OSC_NAME( client_show_optional_gui ), NULL, "client_id" );
    osc_server->add_method( "/nsm/gui/client/hide_optional_gui", "s", OSC_NAME( client_hide_optional_gui ), NULL, "client_id" );

    osc_server->add_method( "/osc/ping", "", OSC_NAME( ping ), NULL, "" );

    osc_server->add_method( "/nsm/server/broadcast", NULL, OSC_NAME( broadcast ), NULL, "" );
    osc_server->add_method( "/nsm/server/duplicate", "s", OSC_NAME( duplicate ), NULL, "" );
    osc_server->add_method( "/nsm/server/abort", "", OSC_NAME( abort ), NULL, "" );
    osc_server->add_method( "/nsm/server/list", "", OSC_NAME( list ), NULL, "" );
    osc_server->add_method( "/nsm/server/add", "s", OSC_NAME( add ), NULL, "executable_name" );
    osc_server->add_method( "/nsm/server/new", "s", OSC_NAME( new ), NULL, "name" );
    osc_server->add_method( "/nsm/server/save", "", OSC_NAME( save ), NULL, "" );
    osc_server->add_method( "/nsm/server/open", "s", OSC_NAME( open ), NULL, "name" );
    osc_server->add_method( "/nsm/server/close", "", OSC_NAME( close ), NULL, "" );
    osc_server->add_method( "/nsm/server/quit", "", OSC_NAME( quit ), NULL, "" );

    osc_server->add_method( NULL, NULL, OSC_NAME( null ),NULL, "" );

   if ( load_session )
   {
        char *spath;
        asprintf( &spath, "%s/%s", session_root, load_session); // Build the session path. --load-session works with --session-root
        MESSAGE( "Loading session given by parameter %s", spath);
        load_session_file( spath );
        free ( spath );
    }

    if ( detach )
    {
        MESSAGE( "Detaching from console" );
        if ( fork() )
        {
            exit( 0 );
        }
        else
        {
            fclose( stdin );
            fclose( stdout );
            fclose( stderr );
        }
    }

    /* listen for sigchld signals and process OSC messages forever */
    int start_ppid = getppid(); //get parent pid
    for ( ;; )
    {
        wait( 1000 ); //1000 ms
        //This still has some corner cases, like a race condition on startup that never gets the real PID, but
        //we cover the majority of cases at least:
        if ( start_ppid != getppid() ) {
            WARNING ( "Our parent PID changed from %d to %d, which indicates a possible GUI crash. The user has no control over the session anymore. Trying to shut down cleanly.", start_ppid, getppid());
            handle_signal_clean_exit ( 0 );
        }
    }

    //Code after here will not be executed if nsmd is stopped with any abort-signal like SIGINT.
    //Without a signal handler clients will remain active ("zombies") without nsmd as parent.
    //Therefore exit is handled by handle_signal_clean_exit()
    return 0;
}
07070100000048000081A40000000000000000000000016258A65C0000010D000000000000000000000000000000000000005300000000new-session-manager-1.6.0+git.20220415.0f6719c/src/org.jackaudio.jackpatch.desktop[Desktop Entry]
Type=Application
Name=JACKpatch
Comment=Remember the JACK Audio Connection Kit Graph in NSM
Exec=jackpatch
Icon=jackpatch
Terminal=false
StartupNotify=false
Version=1.0
Categories=AudioVideo;Audio;
X-NSM-Capable=true
X-NSM-Exec=jackpatch
NoDisplay=true
07070100000049000081A40000000000000000000000016258A65C000001F5000000000000000000000000000000000000005800000000new-session-manager-1.6.0+git.20220415.0f6719c/src/org.jackaudio.nsm-legacy-gui.desktop[Desktop Entry]
Type=Application
Name=New Session Manager (Legacy GUI)
Name[fr]=New Session Manager (ancienne interface)
Comment=Audio session manager (Legacy GUI)
Comment[de]=Verwaltet Audiositzungen (Alte Oberfläche)
Comment[fr]=Gestionnaire de session audio (ancienne interface)
Comment[it]=Gestore di sessioni audio (Vecchia GUI)
Comment[pt]=Gestor de sessões audio (Antiga GUI)
Exec=nsm-legacy-gui
Icon=nsm-legacy-gui
Terminal=false
StartupNotify=false
Version=1.0
Categories=AudioVideo;Audio;
0707010000004A000081A40000000000000000000000016258A65C0000010D000000000000000000000000000000000000005300000000new-session-manager-1.6.0+git.20220415.0f6719c/src/org.jackaudio.nsm-proxy.desktop[Desktop Entry]
Type=Application
Name=NSM-Proxy
Comment=Wrapper for executables without direct NSM-Support.
Exec=nsm-proxy
Icon=nsm-proxy
Terminal=false
StartupNotify=false
Version=1.0
Categories=AudioVideo;Audio;
X-NSM-Capable=true
X-NSM-Exec=nsm-proxy
NoDisplay=true
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!1402 blocks
openSUSE Build Service is sponsored by